<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/Frog_16px_1177822_easyicon.net.ico">
  <link rel="mask-icon" href="/images/Frog_32px_1177822_easyicon.net.ico" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"hxy1997.xyz","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":true,"pangu":false,"comments":{"style":"tabs","active":"valine","storage":true,"lazyload":true,"nav":null,"activeClass":"valine"},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"输入关键字","hits_empty":"没有找到与「${query}」相关搜索","hits_stats":"${hits} 条相关记录，共耗时 ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.json"};
  </script>

  <meta name="description" content="这个Toy-React 是我选择在入职前的练手项目，比较基础，后续亟待完善（主要想用上Typescript）。主要目的是希望它可以作为一把开启React源码大门的钥匙。进一步学习React的原理知识，更加深刻理解React。">
<meta property="og:type" content="article">
<meta property="og:title" content="Toy-React基础版">
<meta property="og:url" content="https://hxy1997.xyz/2022/03/23/Toy-React%E5%9F%BA%E7%A1%80%E7%89%88/index.html">
<meta property="og:site_name" content="hxy的博客">
<meta property="og:description" content="这个Toy-React 是我选择在入职前的练手项目，比较基础，后续亟待完善（主要想用上Typescript）。主要目的是希望它可以作为一把开启React源码大门的钥匙。进一步学习React的原理知识，更加深刻理解React。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407083210.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220406223503.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407132424.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407133433.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407133532.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407173725.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407174623.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407175146.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407180207.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407180613.gif">
<meta property="article:published_time" content="2022-03-23T12:18:33.000Z">
<meta property="article:modified_time" content="2022-04-07T10:09:40.117Z">
<meta property="article:author" content="hxy">
<meta property="article:tag" content="React">
<meta property="article:tag" content="前端">
<meta property="article:tag" content="轮子">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407083210.png">

<link rel="canonical" href="https://hxy1997.xyz/2022/03/23/Toy-React%E5%9F%BA%E7%A1%80%E7%89%88/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Toy-React基础版 | hxy的博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="hxy的博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">hxy的博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Mia san Mia!</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/huxingyi1997" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://hxy1997.xyz/2022/03/23/Toy-React%E5%9F%BA%E7%A1%80%E7%89%88/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/Robben.gif">
      <meta itemprop="name" content="hxy">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="hxy的博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Toy-React基础版
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-03-23 20:18:33" itemprop="dateCreated datePublished" datetime="2022-03-23T20:18:33+08:00">2022-03-23</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-04-07 18:09:40" itemprop="dateModified" datetime="2022-04-07T18:09:40+08:00">2022-04-07</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/web%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">web前端</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="热度" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">热度：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/2022/03/23/Toy-React%E5%9F%BA%E7%A1%80%E7%89%88/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/2022/03/23/Toy-React%E5%9F%BA%E7%A1%80%E7%89%88/" itemprop="commentCount"></span>
    </a>
  </span>
  
  

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>这个Toy-React 是我选择在入职前的练手项目，比较基础，后续亟待完善（主要想用上Typescript）。主要目的是希望它可以作为一把开启React源码大门的钥匙。进一步学习React的原理知识，更加深刻理解React。</p>
<span id="more"></span>

<h1 id="React框架背后的核心机理"><a href="#React框架背后的核心机理" class="headerlink" title="React框架背后的核心机理"></a>React框架背后的核心机理</h1><h2 id="1-项目初始化"><a href="#1-项目初始化" class="headerlink" title="1. 项目初始化"></a>1. 项目初始化</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// 1.创建toy-react文件夹</span><br><span class="line">mkdir toy-react</span><br><span class="line"></span><br><span class="line">// 2.进入toy-react文件夹</span><br><span class="line"><span class="built_in">cd</span> toy-react</span><br><span class="line"></span><br><span class="line">// 3.创建项目</span><br><span class="line">npm init -y</span><br></pre></td></tr></table></figure>

<h2 id="2-配置webpack环境"><a href="#2-配置webpack环境" class="headerlink" title="2. 配置webpack环境"></a>2. 配置webpack环境</h2><h3 id="1-安装webpack"><a href="#1-安装webpack" class="headerlink" title="1.安装webpack"></a>1.安装webpack</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install webpack webpack-cli --save-dev</span><br></pre></td></tr></table></figure>

<h3 id="2-配置webpack"><a href="#2-配置webpack" class="headerlink" title="2.配置webpack"></a>2.配置webpack</h3><p>建立 <code>webpack.config.js </code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// node的标准export</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">	<span class="comment">// 入口文件</span></span><br><span class="line">	entry: &#123;</span><br><span class="line">		main: <span class="string">&#x27;./main.js&#x27;</span></span><br><span class="line">	&#125;</span><br><span class="line">    <span class="comment">// 增加build后文件可读性，不压缩打包后文件</span></span><br><span class="line">    mode: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">    optimization: &#123;</span><br><span class="line">    	minimize: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-配置babel"><a href="#3-配置babel" class="headerlink" title="3.配置babel"></a>3.配置babel</h3><p>安装babel</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">// babel-loader webpack将babel打包到main.js</span><br><span class="line">// @babel/core  babel核心</span><br><span class="line">// @babel/preset-env 将babel转义到所需js版本</span><br><span class="line">// https://www.babeljs.cn/docs/babel-preset-env</span><br><span class="line">npm install babel-loader @babel/core @babel/preset-env --save-dev</span><br></pre></td></tr></table></figure>

<p>在 <code>webpack.config.js </code>中配置 <code>babel-loader </code>， <code>babel-loader</code> 将es高级语法转化为浏览器能够读懂的语法， <code>@babel/preset-env </code>作为预转译插件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="comment">// modules 打包规则</span></span><br><span class="line">    <span class="built_in">module</span>: &#123;</span><br><span class="line">    	rules: [</span><br><span class="line">        	<span class="comment">// js 文件需经过babel</span></span><br><span class="line">        	&#123;</span><br><span class="line">            	test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">                use: &#123;</span><br><span class="line">                	loader: <span class="string">&#x27;babel-loader&#x27;</span>,</span><br><span class="line">                    <span class="comment">// 配置babel-loader</span></span><br><span class="line">                    options: &#123;</span><br><span class="line">                    	presets: [<span class="string">&#x27;@babel/preset-env&#x27;</span>]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>安装babel的jsx插件，<code>@babel/plugin-transform-react-jsx</code> 解析jsx语法糖</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @babel/plugin-transform-react-jsx --save-dev</span><br></pre></td></tr></table></figure>

<p>配置babel的jsx插件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="comment">// modules 打包规则</span></span><br><span class="line">    <span class="built_in">module</span>: &#123;</span><br><span class="line">    	rules: [</span><br><span class="line">        	<span class="comment">// js 文件需经过babel</span></span><br><span class="line">        	&#123;</span><br><span class="line">            	test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">                use: &#123;</span><br><span class="line">                	loader: <span class="string">&#x27;babel-loader&#x27;</span>,</span><br><span class="line">                    <span class="comment">// 配置babel-loader</span></span><br><span class="line">                    options: &#123;</span><br><span class="line">                    	presets: [<span class="string">&#x27;@babel/preset-env&#x27;</span>],</span><br><span class="line">                        plugins: [<span class="string">&#x27;@babel/plugin-transform-react-jsx&#x27;</span>]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为是手写的react<del>借助react-jsx的塑料react</del>，所以我们可以重新配置<code>@babel/plugin-transform-react-jsx</code>，让它看起来没有那么react</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="built_in">module</span>: &#123;</span><br><span class="line">    	rules: [</span><br><span class="line">        	&#123;</span><br><span class="line">            	test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">                use: &#123;</span><br><span class="line">                	loader: <span class="string">&#x27;babel-loader&#x27;</span>,</span><br><span class="line">                    <span class="comment">// 配置babel-loader</span></span><br><span class="line">                    options: &#123;</span><br><span class="line">                    	presets: [<span class="string">&#x27;@babel/preset-env&#x27;</span>],</span><br><span class="line">                        <span class="comment">// 这样打包后的‘React.createElement就会变成‘ToyReact.createElement’</span></span><br><span class="line">                        plugins: [[<span class="string">&#x27;@babel/plugin-transform-react-jsx&#x27;</span>, &#123;<span class="attr">pragma</span>: <span class="string">&#x27;ToyReact.createElement&#x27;</span>&#125;]]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3-实现toyReact"><a href="#3-实现toyReact" class="headerlink" title="3. 实现toyReact"></a>3. 实现toyReact</h2><p>为了更加直观的展示效果, 我们可以将打包后的script文件, 引入到<code>dist/main.html</code>中。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;main.js&quot;</span> &gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>使用<code>@babel/plugin-transform-react-jsx</code>打包之后jsx(<code>main.js</code>)是这个样子</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 打包前的jsx</span></span><br><span class="line"><span class="keyword">let</span> a = <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;a&quot;</span> <span class="attr">class</span>=<span class="string">&quot;c&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 打包后的jsx</span></span><br><span class="line"><span class="keyword">var</span> a = React.createElement(<span class="string">&quot;div&quot;</span>, </span><br><span class="line">&#123;<span class="attr">id</span>: <span class="string">&quot;a&quot;</span>, <span class="string">&quot;class&quot;</span>: <span class="string">&quot;c&quot;</span>&#125;, </span><br><span class="line">React.createElement(<span class="string">&quot;div&quot;</span>, <span class="literal">null</span>), </span><br><span class="line">React.createElement(<span class="string">&quot;div&quot;</span>, <span class="literal">null</span>))</span><br></pre></td></tr></table></figure>

<p>所以我们可以知道，<code>React.createElement()</code>会传入三个参数<code>tagName</code>,<code>tag</code>的属性列表，<code>tag</code>的子元素</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">React.createElement(tagName, attributes, ...children)</span><br></pre></td></tr></table></figure>

<p>那么我们可以实现最简单的<code>createElement</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> createElement (tagName, attributes, ...children) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">document</span>.createElement(tagName)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在为我们的<code>createElement</code>增加属性和子节点的配置</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> createElement (tagName, attributes, ...children) &#123;</span><br><span class="line">        <span class="keyword">let</span> e = <span class="built_in">document</span>.createElement(tagName)</span><br><span class="line">        <span class="comment">// 增加属性</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> attributes) &#123;</span><br><span class="line">            e.setAttribute(i, attributes[i])</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 增加子节点</span></span><br><span class="line">        <span class="comment">// 扩展运算符将children包装为一个数组</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> children) &#123;</span><br><span class="line">            e.appendChild(child)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> e</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们的<code>createElement</code>已经可以实现使用，但是尚未考虑<strong>文本节点</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> createElement (tagName, attributes, ...children) &#123;</span><br><span class="line">        <span class="keyword">let</span> e = <span class="built_in">document</span>.createElement(tagName)</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> attributes) &#123;</span><br><span class="line">            e.setAttribute(i, attributes[i])</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> children) &#123;</span><br><span class="line">            <span class="comment">// 将child创建为文本节点</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="keyword">typeof</span> child === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">                child = <span class="built_in">document</span>.createTextNode(child)</span><br><span class="line">            &#125;</span><br><span class="line">            e.appendChild(child)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> e</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>目前，我们的<code>createElement</code>可以支持基础的dom操作，是合格的语法糖🍬了。</p>
<p>但问题依旧存在：在react的jsx中，小写的tagName对应生成原生dom对象，而大写的tagName则对应<strong>自定义组件</strong>。<code>tagName</code>参数支持传入的，不仅仅是字符串，还支持<strong>class组件</strong>以及<strong>函数组件</strong></p>
<p>故而，<code>tagName</code>其实是<code>tagType</code>参数，我们需要根据<code>tagType</code>生成不同的dom对象 （目前只支持class组件）</p>
<h3 id="3-1-普通节点的wrapper实现"><a href="#3-1-普通节点的wrapper实现" class="headerlink" title="3.1 普通节点的wrapper实现"></a>3.1 普通节点的wrapper实现</h3><p>新建ElementWrapper类,并且新增 <code>创建实体DOM</code>方法 以及 <code>setAttribute</code>和 <code>appendChild</code> 方法。</p>
<p>appendChild方法中<code>component</code> 都是经过 <code>ElementWrapper</code> 或者</p>
<p><code>TextWrapper</code> 实例化后的值。 <code>component.root</code> 指代的即是元素节点或者文本节点</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> </span>&#123;</span><br><span class="line">	<span class="title">constructor</span> (<span class="params">type</span>) &#123;</span><br><span class="line">    	<span class="comment">// 创建根元素</span></span><br><span class="line">    	<span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 配置属性</span></span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">    	<span class="built_in">this</span>.root.setAttribute(name, value)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 添加子元素</span></span><br><span class="line">    <span class="comment">// 添加的是component，所以要取出传入的component的root</span></span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">    	<span class="built_in">this</span>.root.appendChild(component.root)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>新建TextNodeWrapper类, 并且只需要创建一个文本节点即可。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 文本节点不需要设置属性及添加子元素</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TextWrapper</span> </span>&#123;</span><br><span class="line">	<span class="title">constructor</span> (<span class="params">content</span>) &#123;</span><br><span class="line">    	<span class="built_in">this</span>.root = <span class="built_in">document</span>.createTextNode(content)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-2-实现Component类"><a href="#3-2-实现Component类" class="headerlink" title="3.2 实现Component类"></a>3.2 实现Component类</h3><p>自定义组件需要继承Component类，限定它的默认行为</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">	render () &#123;</span><br><span class="line">    	<span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">          &lt;h1&gt;myComponent&lt;/h1&gt;</span><br><span class="line">          &#123;<span class="built_in">this</span>.children&#125;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么模仿react的实现，我们的Component类大概是这个样子：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">	<span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">    	<span class="comment">// 不需要有什么行为</span></span><br><span class="line">        <span class="comment">// 取到props</span></span><br><span class="line">        <span class="built_in">this</span>.props = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">        <span class="built_in">this</span>.children = []</span><br><span class="line">        <span class="comment">// 初始化root</span></span><br><span class="line">        <span class="built_in">this</span>._root = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 把Component的属性存起来</span></span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">    	<span class="built_in">this</span>.props[name] = value</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 添加子元素</span></span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">    	<span class="built_in">this</span>.children.push(component)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 设置 root 的getter</span></span><br><span class="line">    get root () &#123;</span><br><span class="line">    	<span class="keyword">if</span> (!<span class="built_in">this</span>._root) &#123;</span><br><span class="line">        <span class="comment">// 渲染组件，调用组件的render方法</span></span><br><span class="line">        <span class="comment">// 如果render之后是component，则会递归调用，直至其成为elementWrapper或者textWrapper</span></span><br><span class="line">        	<span class="built_in">this</span>._root = <span class="built_in">this</span>.render().root</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>._root</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里可能会有疑问, 为什么需要新增一个额外的类。</p>
<p>当babel解析到<code>TestComponent</code>的时候, 我们直接实例化它, 并且给实例化后的值 <code>setAttribute</code>属性以及<code> appendChild</code> 子节点。 我们当然不能在main.js中写这些方法的具体实现。因此我们让TestComponent去<code>继承</code> Component, 让Component类去实现这两个方法。</p>
<h3 id="3-3-实现render"><a href="#3-3-实现render" class="headerlink" title="3.3 实现render"></a>3.3 实现render</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> render (component, parentElement) &#123;</span><br><span class="line">        <span class="comment">// parentElement为实际dom</span></span><br><span class="line">        parentElement.appendChild(component.root)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="3-4-重构createElement"><a href="#3-4-重构createElement" class="headerlink" title="3.4 重构createElement"></a>3.4 重构<code>createElement</code></h3><p>判断element的类型, 如果是元素标签的字符串类型, 那么就通过ElementWrapper创建实DOM, 否则就直接实例化本身返回其render的jsx, 进行重新调用createElement构建元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> createElement (tagType, attributes, ...children) &#123;</span><br><span class="line">        <span class="keyword">let</span> element</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> tagType === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">            <span class="comment">// 如果是小写的tagName，则生成ElementWrapper对象</span></span><br><span class="line">            element = <span class="keyword">new</span> ElementWrapper(tagType)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果是组件，则生成对应的组件对象</span></span><br><span class="line">            element = <span class="keyword">new</span> tagType</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 增加属性</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> attributes) &#123;</span><br><span class="line">            <span class="comment">// 调用元素的setAttribute方法</span></span><br><span class="line">            element.setAttribute(name, attributes[name])</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 增加子节点</span></span><br><span class="line">        <span class="comment">// 扩展运算符将children包装为一个数组</span></span><br><span class="line">        <span class="keyword">let</span> insertChildren = <span class="function">(<span class="params">children</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> children) &#123;</span><br><span class="line">                <span class="comment">// 如果child为null，不做任何处理</span></span><br><span class="line">                <span class="keyword">if</span> (child === <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 将child创建为文本节点，如果child是文本节点</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">typeof</span> child === <span class="string">&#x27;string&#x27;</span>) &#123;</span><br><span class="line">                    child = <span class="keyword">new</span> TextWrapper(child)</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 当child是数组的时候，即component中的children，需要展开child</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">typeof</span> child === <span class="string">&#x27;object&#x27;</span> &amp;&amp; child <span class="keyword">instanceof</span> <span class="built_in">Array</span>) &#123;</span><br><span class="line">                    <span class="comment">// 递归调用</span></span><br><span class="line">                    insertChildren(child)</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 调用元素的appendChild方法</span></span><br><span class="line">                    element.appendChild(child)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        insertChildren(children)</span><br><span class="line">        <span class="keyword">return</span> element</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们通过一个简单的递归函数去处理child是数组的情况。现在我们的页面可以正常的显示DOM结构, 并且拥有props的能力。 </p>
<h3 id="3-5-main-js文件"><a href="#3-5-main-js文件" class="headerlink" title="3.5 main.js文件"></a>3.5 main.js文件</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Component, ToyReact &#125; <span class="keyword">from</span> <span class="string">&#x27;./toy-react.js&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render () &#123;</span><br><span class="line">        <span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">            &lt;h1&gt;myComponent&lt;/h1&gt;</span><br><span class="line">            &#123;<span class="built_in">this</span>.children&#125;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ToyReact.render(<span class="xml"><span class="tag">&lt;<span class="name">MyComponent</span> <span class="attr">id</span>=<span class="string">&quot;a&quot;</span> <span class="attr">class</span>=<span class="string">&quot;c&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;<span class="name">h2</span>&gt;</span>ssss<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="xml"><span class="tag">&lt;/<span class="name">MyComponent</span>&gt;</span></span>, <span class="built_in">document</span>.body)</span><br></pre></td></tr></table></figure>

<h2 id="4-一些笔记"><a href="#4-一些笔记" class="headerlink" title="4. 一些笔记"></a>4. 一些笔记</h2><p>通过<a target="_blank" rel="noopener" href="https://www.babeljs.cn/docs/babel-preset-env">阅读Babel官网</a>，了解到@babel/plugin-transform-react-jsx插件有两种编译jsx的方式：</p>
<ul>
<li>运行时编译方式（React Automatic Runtime）</li>
<li>手动引入React.createElement的方式（React Classic Runtime）</li>
</ul>
<p>这解释了困扰我的两个问题：</p>
<blockquote>
<p>1、为什么定义了createElement方法却没有调用？</p>
<p>因为babel jsx转换插件是“运行时编译”且pagram参数为createElement，所以代码编译时会自动解析jsx并调用createElement方法。</p>
</blockquote>
<blockquote>
<p>2、为什么render方法里父节点要接收一个component.root作为参数而不是component？</p>
<p>同样是由于babel jsx插件的“运行时”编译，调用createElement方法后会实例化一个Component对象，该对象初始root为null，从而会调用render方法（即MyComponent中的render方法），该方法返回一个JSX，从而又会调用createElement方法，此时是一个真实的DOM节点，所以会初始化一个ElementWrapper对象，该对象包含一个root属性，这时root就不为null了，而是一个div</p>
</blockquote>
<h1 id="为toy-react添加生命周期"><a href="#为toy-react添加生命周期" class="headerlink" title="为toy-react添加生命周期"></a>为toy-react添加生命周期</h1><p>在实现了ToyReact的自定义组件的jsx语法后，我们的ToyReact可以跑起来啦</p>
<p>✿✿ヽ(°▽°)ノ✿</p>
<p>但是，ToyReact不能自动更新，只是一个空壳子</p>
<p>(⊙︿⊙)</p>
<p>现在就让我们实现dom的更新，以及setState的支持吧~</p>
<h2 id="1-state的实现"><a href="#1-state的实现" class="headerlink" title="1. state的实现"></a>1. state的实现</h2><p>在react中，自定义组件拥有自己的state，所以我们可以理解为，<code>Component</code>是不需要有自己的state，但需要设置setState方法，已使自定义组件调用setState</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">	<span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">    	<span class="comment">// 执行Component的构造函数</span></span><br><span class="line">    	<span class="built_in">super</span>()</span><br><span class="line">        <span class="built_in">this</span>.state = &#123;</span><br><span class="line">        	a: <span class="number">1</span>,</span><br><span class="line">            b: <span class="number">2</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    render () &#123;</span><br><span class="line">    	<span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">     		&lt;h1&gt;MyComponent&lt;/h1&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.a.toString()&#125;&lt;/span&gt;</span><br><span class="line">            &#123;<span class="built_in">this</span>.children&#125;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="2-ToyReact-dom更新"><a href="#2-ToyReact-dom更新" class="headerlink" title="2. ToyReact dom更新"></a>2. ToyReact dom更新</h2><p>之前的render函数是基于root实现的，是一个“取得自定义组件的root -&gt; 取得root中自定义组件的root -&gt; 递归直至TextWrapper或者ElementWrapper(包含真正的root)”</p>
<p>要更新dom，我们需要锁定节点的位置。在react中，因为采用了虚拟dom，所以更新dom会十分精巧。而目前ToyReact采用的是实际dom，所以**更新dom意味着重新渲染整个<code>this.root</code>**，但是我们依旧可以通过<code>Range</code>API实现dom的定位</p>
<p>我们先来了解一下一个不太常用的api <code>range</code>：<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Range"> Range的MDN文档</a></p>
<h3 id="range的定义和-API的简单使用"><a href="#range的定义和-API的简单使用" class="headerlink" title="range的定义和 API的简单使用"></a>range的定义和 API的简单使用</h3><p>MDN是这样定义它的: <code>Range 接口表示一个包含节点与文本节点的一部分的文档片段</code>.我认为在此基础上,稍稍修改一下会更好理解。 <code> Range 接口能够表示文档中任意节点之间的一部分文档(HTML DOM)片段。</code>。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span> hello<span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li><p>Range.setStart(startNode, startOffset) 设置Range的起点</p>
<p>接收二个参数第一个参数是节点, 第二个参数是节点的偏移量。比如拿上面的例子来说:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p1 = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;p1&#x27;</span>)</span><br><span class="line">range.setStart(p1, <span class="number">1</span>)</span><br></pre></td></tr></table></figure>

<p>range的起始位置应该是</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">            range起始位置</span><br><span class="line">                |</span><br><span class="line">                |</span><br><span class="line">                |  </span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span>hello <span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>那么如果<code>setStart</code>的第二个参数是0,那么range的起始位置则是:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">        range起始位置</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">            |  </span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span>hello <span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>其实很容易理解，p1元素节点下面有三个子节点。一个是文本节点hello, 另外两个则是元素节点 <code>&lt;span&gt; world !&lt;/span&gt;</code>。</p>
</li>
<li><p>Range.setEnd(startNode, startOffset) 设置Range的结束位置。</p>
<p>接收二个参数第一个参数是节点, 第二个参数是节点的偏移量。我们还是拿上面的例子来说:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> p1 = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;p1&#x27;</span>)</span><br><span class="line">range.setEnd(p1, p1.childNodes.length)</span><br></pre></td></tr></table></figure>

<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">													range结束位置</span><br><span class="line">                                                        |</span><br><span class="line">                                                        |</span><br><span class="line">                                                        |  </span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span>hello <span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span> <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>Range.insertNode(Node) 在Range的起始位置插入节点。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span> hello<span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world!<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line"><span class="keyword">const</span> p1 = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;p1&#x27;</span>)</span><br><span class="line"><span class="keyword">const</span> element = <span class="built_in">document</span>.createElement(<span class="string">&#x27;p&#x27;</span>)</span><br><span class="line">element.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&#x27;123&#x27;</span>))</span><br><span class="line">range.setStart(p1, <span class="number">0</span>)</span><br><span class="line">range.setEnd(p1, p1.childNodes.length)</span><br><span class="line">range.insertNode(element)</span><br></pre></td></tr></table></figure>

<p>当执行完<code>insertNode</code> 方法后,会在文本节点hello前面添加一个p元素节点。</p>
</li>
<li><p>Range.deleteContents() 移除来自 Document的Range 内容。</p>
<p>调用此方法会删除range内的所有节点。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">id</span>=<span class="string">&quot;p1&quot;</span>&gt;</span> hello<span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world !<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;<span class="name">span</span>&gt;</span> world!<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line"><span class="keyword">const</span> p1 = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;p1&#x27;</span>)</span><br><span class="line">range.setStart(p1, <span class="number">0</span>)</span><br><span class="line">range.setEnd(p1, p1.childNodes.length)</span><br><span class="line">range.deleteContents()</span><br></pre></td></tr></table></figure>

</li>
</ul>
<p>以上代码执行后p1节点下面的所有节点都将被删除。</p>
<blockquote>
<p>range的其他api本篇文章中不会涉及,因此就不一一介绍了。</p>
</blockquote>
<h3 id="2-1-重写Component的get-root"><a href="#2-1-重写Component的get-root" class="headerlink" title="2.1 重写Component的get root"></a>2.1 重写Component的get root</h3><p>我们为什么要用range去重构之前的代码呢？我认为主要是出于以下的考虑:</p>
<ul>
<li>1.使用range我们可以在任意节点处插入DOM</li>
<li>2.为接下来的重新渲染与虚拟DOM的比对做铺垫</li>
</ul>
<p>我们修改的基本思路是:</p>
<ul>
<li>从渲染DOM的地方开始着手, 使用range去完成DOM的实际操作</li>
<li>仔细阅读之前的代码, 你会发现它无法进行重新渲染。因此我们需要定义一个私有的方法能够让DOM树重新render。</li>
</ul>
<p>为了让渲染DOM树的方法, 变得不那么容易让外部调用, 我们使用<code>Symbol</code> 返回的唯一标识符作为函数名。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过symbol定义方法名称，保证私有性</span></span><br><span class="line"><span class="keyword">const</span> RENDER_TO_DOM = <span class="built_in">Symbol</span>(<span class="string">&quot;render to dom&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.props = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">        <span class="built_in">this</span>.children = []</span><br><span class="line">        <span class="built_in">this</span>._root = <span class="literal">null</span></span><br><span class="line">        <span class="built_in">this</span>._range = <span class="literal">null</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">        <span class="built_in">this</span>.props[name] = value</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">        <span class="built_in">this</span>.children.push(component)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用[]将Symbol作为函数名</span></span><br><span class="line">    <span class="comment">// 传入的是range</span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	<span class="comment">// 组件的this.render()会返回组件，之后再调用组件的[RENDER_TO_DOM]方法</span></span><br><span class="line">    	<span class="built_in">this</span>.render()[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 之前的get root 方法被[RENDER_TO_DOM]替代</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们在Component类中添加一个私有的方法, 因为this.render()返回的值有可能是一个Component, ElementWrapper, TextWrapper。因此在其余二个类中, 我们 也需要去添加<code>RENDER_TO_DOM</code> 方法。</p>
<h3 id="2-2-重写TextWrapper和ElementWrapper"><a href="#2-2-重写TextWrapper和ElementWrapper" class="headerlink" title="2.2 重写TextWrapper和ElementWrapper"></a>2.2 重写TextWrapper和ElementWrapper</h3><p>在更新了Component的<code>[RENDER_TO_DOM]</code>方法之后，需要在TextWrapper和ElementWrapper中增加对应的<code>[RENDER_TO_DOM]</code>方法，将实DOM渲染到页面。因此在<code>RENDER_TO_DOM</code> 中我们需要往range中插入节点。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">type</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root.setAttribute(name, value)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 增加[RENDER_TO_DOM]方法</span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	<span class="comment">// 首先从文档中移除 Range 包含的内容。</span></span><br><span class="line">    	range.deleteContents()</span><br><span class="line">        <span class="comment">// 再将root插入range，完成渲染</span></span><br><span class="line">        range.insertNode(<span class="built_in">this</span>.root)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 由于采用了range，所以增加child也要修改</span></span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">    	<span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">    	<span class="comment">// 将新增的元素置于range末尾</span></span><br><span class="line">    	range.setStart(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	range.setEnd(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	component[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 文本节点不需要设置属性及添加子元素</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TextWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">content</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createTextNode(content)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 增加[RENDER_TO_DOM]方法</span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	<span class="comment">// 首先从文档中移除 Range 包含的内容。</span></span><br><span class="line">    	range.deleteContents()</span><br><span class="line">        <span class="comment">// 再将root插入range，完成渲染</span></span><br><span class="line">        range.insertNode(<span class="built_in">this</span>.root)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-3-更新render函数"><a href="#2-3-更新render函数" class="headerlink" title="2.3 更新render函数"></a>2.3 更新render函数</h3><p>由于我们不再使用<code>get root()</code> 方法来获取实DOM, 因此我们通过调用 <code>RENDER_TO_DOM</code> 来插入节点。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> render (component, parentElement) &#123;</span><br><span class="line">        <span class="comment">// 在parentElement尾部增加range</span></span><br><span class="line">        <span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 将range的start节点设置为parentElement，offset为0，说明range将包含parentElement的全部children</span></span><br><span class="line">        range.setStart(parentElement, <span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 因为parentElement中会有文本节点和注释节点，所以offset不是parentElement.children.length</span></span><br><span class="line">        range.setEnd(parentElement, parentElement.childNodes.length)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 清空range</span></span><br><span class="line">        range.deleteContents()</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 调用[RENDER_TO_DOM]方法</span></span><br><span class="line">        component[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-4-支持重新绘制dom"><a href="#2-4-支持重新绘制dom" class="headerlink" title="2.4 支持重新绘制dom"></a>2.4 支持重新绘制dom</h3><p>需要修改<code>[RENDER_TO_DOM]</code>方法</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">type</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root.setAttribute(name, value)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 支持dom重绘</span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	range.deleteContents()</span><br><span class="line">        range.insertNode(<span class="built_in">this</span>.root)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 由于采用了range，所以增加child也要修改</span></span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">    	<span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">    	<span class="comment">// 将新增的元素置于range末尾</span></span><br><span class="line">    	range.setStart(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	range.setEnd(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	component[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 文本节点不需要设置属性及添加子元素</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TextWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">content</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createTextNode(content)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 增加[RENDER_TO_DOM]方法</span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	range.deleteContents()</span><br><span class="line">        range.insertNode(<span class="built_in">this</span>.root)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.props = <span class="built_in">Object</span>.create(<span class="literal">null</span>)</span><br><span class="line">        <span class="built_in">this</span>.children = []</span><br><span class="line">        <span class="built_in">this</span>._root = <span class="literal">null</span></span><br><span class="line">        <span class="comment">// 初始化_range</span></span><br><span class="line">        <span class="built_in">this</span>._range = <span class="literal">null</span>      </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">        <span class="built_in">this</span>.props[name] = value</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">        <span class="built_in">this</span>.children.push(component)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	<span class="built_in">this</span>._range = range</span><br><span class="line">    	<span class="built_in">this</span>.render()[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 定义重绘方法</span></span><br><span class="line">    rerender () &#123;</span><br><span class="line">    	<span class="built_in">this</span>._range.deleteContents()</span><br><span class="line">        <span class="built_in">this</span>[RENDER_TO_DOM](<span class="built_in">this</span>._range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这样，我们就可以在自定义组件中调用重绘方法，支持setState的操作了。我们需要有一个主动的行为去更新页面。 我们在页面添加一个计数器, 每点击一次按钮, 页面上的数字加一.</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">	<span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">    	<span class="comment">// 执行Component的构造函数</span></span><br><span class="line">    	<span class="built_in">super</span>()</span><br><span class="line">        <span class="built_in">this</span>.state = &#123;</span><br><span class="line">        	a: <span class="number">1</span>,</span><br><span class="line">            b: <span class="number">2</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    render () &#123;</span><br><span class="line">    	<span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">            &lt;h1&gt;MyComponent&lt;/h1&gt;</span><br><span class="line">            &lt;button onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                <span class="built_in">this</span>.state.a++</span><br><span class="line">                <span class="built_in">this</span>.rerender()</span><br><span class="line">            &#125;&#125;&gt;&lt;/button&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.a.toString()&#125;&lt;/span&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.b.toString()&#125;&lt;/span&gt;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="2-5-支持自定义事件事件绑定"><a href="#2-5-支持自定义事件事件绑定" class="headerlink" title="2.5 支持自定义事件事件绑定"></a>2.5 支持自定义事件事件绑定</h3><p>我们点击onClick页面似乎没有反应。其实这边有二个很重要的点没有处理:</p>
<ul>
<li>我们需要处理类似<code>onClick</code> 等事件</li>
<li>我们需要将改变后count的值重新渲染到页面上</li>
</ul>
<p>首先只有元素节点上才能绑定事件, 因此我们肯定是在<code>ElementWrapper</code>类中进行修改。我们写一个简单的正则来匹配所有on开头的事件, 比如onClick, onHover, onMouseUp…..。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">type</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 支持事件绑定</span></span><br><span class="line">    setAttribute (name, value) &#123;</span><br><span class="line">    	<span class="comment">// 采用正则，判断name是否为on开头</span></span><br><span class="line">        <span class="keyword">if</span> (name.match(<span class="regexp">/^on([\s\S]+)/</span>) &#123;</span><br><span class="line">        	<span class="comment">// [\s\S] 表示全部字符 \s为非空白，\S为空白，两个集合互补</span></span><br><span class="line">            <span class="comment">// 由于此处采用match，所以RegExp.$1将拿到匹配的字符，即on之后的部分</span></span><br><span class="line">            <span class="comment">// RegExp.$1.replace(/^[\s\S]/, c =&gt; c.toLowerCase())</span></span><br><span class="line">            <span class="comment">// 确保事件名小写，将第一个字母转换为小写</span></span><br><span class="line">            <span class="built_in">this</span>.root.addEventListener(<span class="built_in">RegExp</span>.$1.replace(<span class="regexp">/^[\s\S]/</span>, <span class="function"><span class="params">c</span> =&gt;</span> c.toLowerCase()), value)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        	<span class="comment">// 其他属性，直接调用root的setAttribute方法</span></span><br><span class="line">        	<span class="built_in">this</span>.root.setAttribute(name, value)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">    	range.deleteContents()</span><br><span class="line">        range.insertNode(<span class="built_in">this</span>.root)</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">    	<span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">    	range.setStart(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	range.setEnd(<span class="built_in">this</span>.root, <span class="built_in">this</span>.root.childNodes.length)</span><br><span class="line">    	component[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3-实现setState"><a href="#3-实现setState" class="headerlink" title="3. 实现setState"></a>3. 实现setState</h2><p>目前，我们的state可以实现更新，但是并不能实现state原有状态的存储，只是单纯的覆盖，所以我们要完善setState，实现state的深拷贝，更新setState方法主要是将新的state与老的state比较, 然后进行一个深拷贝的操作。如果this.state不存在或者类型不是对象的时候, 我们直接使用新的state去替换它。 然后通过递归将新的state中的值直接赋值到旧的对应的state值。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Component的setState方法</span></span><br><span class="line">setState (newState) &#123;</span><br><span class="line">    <span class="comment">// state为null时的处理</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.state === <span class="literal">null</span> || <span class="keyword">typeof</span> <span class="built_in">this</span>.state !== <span class="string">&quot;object&quot;</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果state为null或不是对象，直接为state赋值newState，并重新渲染组件</span></span><br><span class="line">        <span class="built_in">this</span>.state = newState</span><br><span class="line">        <span class="built_in">this</span>.rerender()</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 采用递归的方式访问state</span></span><br><span class="line">    <span class="keyword">let</span> merge = <span class="function">(<span class="params">oldState, newState</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> key <span class="keyword">in</span> newState) &#123;</span><br><span class="line">            <span class="keyword">if</span> (oldState[key] === <span class="literal">null</span> || <span class="keyword">typeof</span> oldState[key] !== <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">                oldState[key] = newState[key]</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 如果oldSate的p属性为对象，那么就递归调用merge，实现深拷贝</span></span><br><span class="line">                merge(oldState[key], newState[key])</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    merge(<span class="built_in">this</span>.state, newState)</span><br><span class="line">    <span class="built_in">this</span>.rerender()</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params"></span>) &#123;</span><br><span class="line">        <span class="comment">// 执行Component的构造函数</span></span><br><span class="line">        <span class="built_in">super</span>()</span><br><span class="line">        <span class="built_in">this</span>.state = &#123;</span><br><span class="line">            a: <span class="number">1</span>,</span><br><span class="line">            b: <span class="number">2</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    render () &#123;</span><br><span class="line">        <span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">            &lt;h1&gt;MyComponent&lt;/h1&gt;</span><br><span class="line">            &lt;button onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                    <span class="built_in">this</span>.setState(&#123;<span class="attr">a</span>: <span class="built_in">this</span>.state.a + <span class="number">1</span>&#125;);</span><br><span class="line">                &#125;&#125;&gt;&lt;/button&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.a.toString()&#125;&lt;/span&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.b.toString()&#125;&lt;/span&gt;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>此时的main.js全部内容正常通过编译和运行</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Component, ToyReact &#125; <span class="keyword">from</span> <span class="string">&#x27;./toy-react.js&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyComponent</span> <span class="keyword">extends</span> <span class="title">Component</span></span>&#123;</span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="comment">// 执行Component的构造函数</span></span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">        <span class="built_in">this</span>.state = &#123;</span><br><span class="line">            a: <span class="number">1</span>,</span><br><span class="line">            b: <span class="number">2</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> &lt;div&gt;</span><br><span class="line">            &lt;h1&gt;my component&lt;/h1&gt;</span><br><span class="line">            &lt;button onClick=&#123;<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                    <span class="built_in">this</span>.setState(&#123;<span class="attr">a</span>: <span class="built_in">this</span>.state.a + <span class="number">1</span>&#125;);</span><br><span class="line">                &#125;&#125;&gt;add&lt;/button&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.a.toString()&#125;&lt;/span&gt;</span><br><span class="line">            &lt;span&gt;&#123;<span class="built_in">this</span>.state.b.toString()&#125;&lt;/span&gt;</span><br><span class="line">            &#123;<span class="built_in">this</span>.children&#125;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ToyReact.render(<span class="xml"><span class="tag">&lt;<span class="name">MyComponent</span> <span class="attr">id</span>=<span class="string">&quot;a&quot;</span> <span class="attr">class</span>=<span class="string">&quot;c&quot;</span>&gt;</span></span></span><br><span class="line"><span class="xml">           <span class="tag">&lt;<span class="name">div</span>&gt;</span>abc<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">           <span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">           <span class="tag">&lt;<span class="name">div</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">       <span class="tag">&lt;/<span class="name">MyComponent</span>&gt;</span></span>, <span class="built_in">document</span>.body)</span><br></pre></td></tr></table></figure>

<h2 id="4-集成React官网示例TicTacToe-demo"><a href="#4-集成React官网示例TicTacToe-demo" class="headerlink" title="4. 集成React官网示例TicTacToe demo"></a>4. 集成React官网示例TicTacToe demo</h2><p><a target="_blank" rel="noopener" href="https://codepen.io/gaearon/pen/VbbVLg">TicTacToe是react的官方教程</a>，我们拿现在toyReact试着跑一下中间的一个过渡例子，顺便对ToyReact进行一些小修小补吧~</p>
<p>🏃🏃🏃</p>
<p>不负众望✿✿ヽ(°▽°)ノ✿没有运行起来</p>
<p>调试后发现，主要问题存在于 createElement函数的insertChildren操作，没有兼顾到children为null的状况，而TicTacToe demo采用了children为null的设置</p>
<p>我们来修复这个问题</p>
<h3 id="4-1-insertChildren支持child为null"><a href="#4-1-insertChildren支持child为null" class="headerlink" title="4.1 insertChildren支持child为null"></a>4.1 insertChildren支持child为null</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">ToyReact</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> createElement (tagType, attributes, ...children) &#123;</span><br><span class="line">        ... ...</span><br><span class="line"></span><br><span class="line">        <span class="keyword">let</span> insertChildren = <span class="function">(<span class="params">children</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> children) &#123;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 如果child为null，不做任何处理</span></span><br><span class="line">                <span class="keyword">if</span> (child === <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">continue</span></span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">typeof</span> child === <span class="string">&quot;string&quot;</span>) &#123;</span><br><span class="line">                    child = <span class="keyword">new</span> TextWrapper(child)</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">typeof</span> child === <span class="string">&quot;object&quot;</span> &amp;&amp; child <span class="keyword">instanceof</span> <span class="built_in">Array</span>) &#123;</span><br><span class="line">                    insertChildren(child)</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    element.appendChild(child)</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        insertChildren(children)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> element</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>需要注意的是，demo中的采用了函数组件，我们需要修改为class组件。另外，我们的toyReact目前还没有支持className的绑定</p>
<p>我们需要在ElementWrapper的setAttribute方法中，实现className的绑定</p>
<h3 id="4-2-className的绑定"><a href="#4-2-className的绑定" class="headerlink" title="4.2 className的绑定"></a>4.2 className的绑定</h3><p>修改ElementWrapper类支持className。我们需要单独处理<code>className</code>这个属性, 因为元素节点的类名是通过赋值到class上才能生效</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> </span>&#123;</span><br><span class="line">    <span class="title">constructor</span> (<span class="params">type</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 采用正则，判断name是否为on开头</span></span><br><span class="line">    <span class="keyword">if</span> (name.match(<span class="regexp">/^on([\s\S]+)/</span>)) &#123;</span><br><span class="line">        <span class="comment">// [\s\S] 表示全部字符 \s为非空白，\S为空白，两个集合互补</span></span><br><span class="line">        <span class="comment">// 由于此处采用match，所以RegExp.$1将拿到匹配的字符，即on之后的部分</span></span><br><span class="line">        <span class="comment">// RegExp.$1.replace(/^[\s\S]/, c =&gt; c.toLowerCase())</span></span><br><span class="line">        <span class="comment">// 确保事件名小写，将第一个字母转换为小写</span></span><br><span class="line">        <span class="built_in">this</span>.root.addEventListener(<span class="built_in">RegExp</span>.$1.replace(<span class="regexp">/^[\s\S]/</span>, <span class="function"><span class="params">c</span> =&gt;</span> c.toLowerCase()), value)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (name === <span class="string">&#x27;className&#x27;</span>) &#123; <span class="comment">// 配置属性</span></span><br><span class="line">        <span class="built_in">this</span>.root.setAttribute(<span class="string">&#x27;class&#x27;</span>, value)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.root.setAttribute(name, value)</span><br><span class="line">    &#125;</span><br><span class="line">   </span><br><span class="line">    appendChild (component) &#123;</span><br><span class="line">        <span class="built_in">this</span>.root.appendChild(component.root)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4-3-修复由于range被吞导致的问题"><a href="#4-3-修复由于range被吞导致的问题" class="headerlink" title="4.3 修复由于range被吞导致的问题"></a>4.3 修复由于range被吞导致的问题</h3><p>我们通过range API实现了组件的重新渲染，但是我们使用<code>range.deleteContents()</code>的时机，导致TicTacToe demo在重新渲染时，存在丢失dom的情况，所以需要修复</p>
<p>问题存在于Component的<code>rerender</code>方法，函数执行时，会先清空range，这会导致空range被相邻的range“吞了”，所以在<code>rerender</code>执行时，需要保证range不空。</p>
<p>所以，我们需要先插入range，再删除它</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    rerender () &#123;</span><br><span class="line">        <span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">        <span class="comment">// 新创建的range没有宽度</span></span><br><span class="line">        range.setStart(<span class="built_in">this</span>._range.startContainer, <span class="built_in">this</span>._range.startOffset)</span><br><span class="line">        range.setEnd(<span class="built_in">this</span>._range.startContainer, <span class="built_in">this</span>._range.startOffset)</span><br><span class="line">        <span class="built_in">this</span>[RENDER_TO_DOM](range)</span><br><span class="line">        <span class="built_in">this</span>._range.deleteContents()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>但是在Component<code>[RENDER_TO_DOM]</code>方法中，我们保留了<code>this._range</code>，所以不能直接在<code>rerender</code>时直接清空</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="comment">// [RENDER_TO_DOM]方法保留了this._range </span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">        <span class="built_in">this</span>._range = range</span><br><span class="line">        <span class="built_in">this</span>.render()[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以我们需要改进一下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    rerender () &#123;</span><br><span class="line">        <span class="comment">// 保存this._range</span></span><br><span class="line">        <span class="keyword">let</span> oldRange = <span class="built_in">this</span>._range</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 新创建的range没有宽度，但会改变oldRange的宽度</span></span><br><span class="line">        <span class="comment">// 新创建的range在this._range的start处</span></span><br><span class="line">        <span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">        range.setStart(oldRange.startContainer, oldRange.startOffset)</span><br><span class="line">        range.setEnd(oldRange.startContainer, oldRange.startOffset)</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>[RENDER_TO_DOM](range)</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 重设oldRange的start节点，跳过插入的range</span></span><br><span class="line">        oldRange.setStart(range.endContainer, range.endOffset)</span><br><span class="line">        <span class="comment">// 清除oldRange的内容</span></span><br><span class="line">        oldRange.deleteContents()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// [RENDER_TO_DOM]方法保留了this._range </span></span><br><span class="line">    [RENDER_TO_DOM] (range) &#123;</span><br><span class="line">        <span class="built_in">this</span>._range = range</span><br><span class="line">        <span class="built_in">this</span>.render()[RENDER_TO_DOM](range)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在我们用<a target="_blank" rel="noopener" href="https://codepen.io/gaearon/pen/gWWZgR">TicTacToe的最终结果</a>验证写好的toyReact，这里我用了之前我自学时写好的，当然要注意函数式组件当前不可用，需要改为class声明。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; Component, ToyReact &#125; <span class="keyword">from</span> <span class="string">&#x27;./toy-react.js&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Square 组件渲染了一个单独的 &lt;button&gt;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Square</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            <span class="comment">// 接收父组件参数</span></span><br><span class="line">            &lt;button className=<span class="string">&quot;square&quot;</span> onClick=&#123;<span class="function">() =&gt;</span> <span class="built_in">this</span>.props.onClick()&#125;&gt;</span><br><span class="line">                &#123;<span class="comment">/* 使用父组件参数 */</span>&#125;</span><br><span class="line">                &#123;<span class="built_in">this</span>.props.value&#125;</span><br><span class="line">            &lt;/button&gt;</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Board 组件渲染了 9 个方块</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Board</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="title">renderSquare</span>(<span class="params">i</span>)</span> &#123;</span><br><span class="line">        <span class="comment">// return &lt;Square /&gt;;</span></span><br><span class="line">        <span class="comment">// 增加参数，修改一下 Square 的点击事件监听函数</span></span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            &lt;Square</span><br><span class="line">                <span class="comment">// 从 Game 组件中接收 squares 和 onClick 这两个 props。</span></span><br><span class="line">                value=&#123;<span class="built_in">this</span>.props.squares[i]&#125;</span><br><span class="line">                onClick=&#123;<span class="function">() =&gt;</span> <span class="built_in">this</span>.props.onClick(i)&#125;</span><br><span class="line">                /&gt;</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            &lt;div&gt;</span><br><span class="line">                &lt;div className=<span class="string">&quot;board-row&quot;</span>&gt;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">0</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">1</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">2</span>)&#125;</span><br><span class="line">                &lt;/div&gt;</span><br><span class="line">                &lt;div className=<span class="string">&quot;board-row&quot;</span>&gt;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">3</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">4</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">5</span>)&#125;</span><br><span class="line">                &lt;/div&gt;</span><br><span class="line">                &lt;div className=<span class="string">&quot;board-row&quot;</span>&gt;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">6</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">7</span>)&#125;</span><br><span class="line">                    &#123;<span class="built_in">this</span>.renderSquare(<span class="number">8</span>)&#125;</span><br><span class="line">                &lt;/div&gt;</span><br><span class="line">            &lt;/div&gt;</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Game 组件渲染了含有默认值的一个棋盘</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Game</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 点击事件</span></span><br><span class="line">    <span class="function"><span class="title">handleClick</span>(<span class="params">i</span>)</span> &#123;</span><br><span class="line">        <span class="comment">// 历史,丢弃stepNumber后的数据</span></span><br><span class="line">        <span class="keyword">const</span> history = <span class="built_in">this</span>.state.history.slice(<span class="number">0</span>, <span class="built_in">this</span>.state.stepNumber + <span class="number">1</span>)</span><br><span class="line">        <span class="comment">// 当前状态</span></span><br><span class="line">        <span class="keyword">const</span> current = history[history.length - <span class="number">1</span>]</span><br><span class="line">        <span class="keyword">const</span> squares = current.squares.slice()</span><br><span class="line">        <span class="comment">// 当有玩家胜出时，或者某个 Square 已经被填充时，该函数不做任何处理直接返回</span></span><br><span class="line">        <span class="keyword">if</span> (calculateWinner(squares) || squares[i]) <span class="keyword">return</span></span><br><span class="line">        <span class="comment">// 根据xIsNext判断</span></span><br><span class="line">        squares[i] = <span class="built_in">this</span>.state.xIsNext ? <span class="string">&quot;X&quot;</span> : <span class="string">&quot;O&quot;</span></span><br><span class="line">        <span class="built_in">this</span>.setState(&#123;</span><br><span class="line">            history: history.concat([</span><br><span class="line">                &#123;</span><br><span class="line">                    squares: squares,</span><br><span class="line">                &#125;,</span><br><span class="line">            ]),</span><br><span class="line">            <span class="comment">// squares: squares,</span></span><br><span class="line">            <span class="comment">// 更新时间步</span></span><br><span class="line">            stepNumber: history.length,</span><br><span class="line">            <span class="comment">// 翻转xIsNext</span></span><br><span class="line">            xIsNext: !<span class="built_in">this</span>.state.xIsNext,</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新状态 stepNumber</span></span><br><span class="line">    <span class="function"><span class="title">jumpTo</span>(<span class="params">step</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.setState(&#123;</span><br><span class="line">            stepNumber: step,</span><br><span class="line">            xIsNext: step % <span class="number">2</span> === <span class="number">0</span>,</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 为 Game 组件添加构造函数，保存历史步骤列表</span></span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(props)</span><br><span class="line">        <span class="built_in">this</span>.state = &#123;</span><br><span class="line">            history: [</span><br><span class="line">                &#123;</span><br><span class="line">                    squares: <span class="built_in">Array</span>(<span class="number">9</span>).fill(<span class="literal">null</span>),</span><br><span class="line">                &#125;,</span><br><span class="line">            ],</span><br><span class="line">            <span class="comment">// 步数</span></span><br><span class="line">            stepNumber: <span class="number">0</span>,</span><br><span class="line">            <span class="comment">// 将 “X” 默认设置为先手棋</span></span><br><span class="line">            xIsNext: <span class="literal">true</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="title">render</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">        <span class="comment">// 状态变化，从子组件提升到父组件</span></span><br><span class="line">        <span class="comment">// 历史</span></span><br><span class="line">        <span class="keyword">const</span> history = <span class="built_in">this</span>.state.history</span><br><span class="line">        <span class="comment">// 当前状态，将代码从始终根据最后一次移动渲染修改为根据当前 stepNumber 渲染</span></span><br><span class="line">        <span class="keyword">const</span> current = history[<span class="built_in">this</span>.state.stepNumber]</span><br><span class="line">        <span class="comment">// 计算胜者</span></span><br><span class="line">        <span class="keyword">const</span> winner = calculateWinner(current.squares)</span><br><span class="line">        <span class="comment">// 历史步骤映射为代表按钮的 React 元素，然后可以展示出一个按钮的列表，点击这些按钮，可以“跳转”到对应的历史步骤。</span></span><br><span class="line">        <span class="keyword">const</span> moves = history.map(<span class="function">(<span class="params">step, move</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> desc = move ? <span class="string">&quot;跳转至第&quot;</span> + move + <span class="string">&quot;步&quot;</span> : <span class="string">&quot;游戏重新开始&quot;</span></span><br><span class="line">            <span class="keyword">return</span> (</span><br><span class="line">                <span class="comment">// 组件的 key 值并不需要在全局都保证唯一，只需要在当前的同一级元素之前保证唯一即可</span></span><br><span class="line">                &lt;li key=&#123;move&#125;&gt;</span><br><span class="line">                    &lt;button onClick=&#123;<span class="function">() =&gt;</span> <span class="built_in">this</span>.jumpTo(move)&#125;&gt;&#123;desc&#125;&lt;/button&gt;</span><br><span class="line">                &lt;/li&gt;</span><br><span class="line">            )</span><br><span class="line">        &#125;)</span><br><span class="line">        <span class="comment">// 状态</span></span><br><span class="line">        <span class="keyword">let</span> status</span><br><span class="line">        <span class="comment">// 判断是否获胜</span></span><br><span class="line">        <span class="keyword">if</span> (winner) &#123;</span><br><span class="line">            status = <span class="string">&quot;胜者：&quot;</span> + winner</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            status = <span class="string">&quot;下一步: &quot;</span> + (<span class="built_in">this</span>.state.xIsNext ? <span class="string">&quot;X&quot;</span> : <span class="string">&quot;O&quot;</span>)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">            &lt;div className=<span class="string">&quot;game&quot;</span>&gt;</span><br><span class="line">                &lt;div className=<span class="string">&quot;game-board&quot;</span>&gt;</span><br><span class="line">                    &lt;Board</span><br><span class="line">                        <span class="comment">// 绑定参数和事件</span></span><br><span class="line">                        squares=&#123;current.squares&#125;</span><br><span class="line">                        onClick=&#123;<span class="function">(<span class="params">i</span>) =&gt;</span> <span class="built_in">this</span>.handleClick(i)&#125;</span><br><span class="line">                        /&gt;</span><br><span class="line">                &lt;/div&gt;</span><br><span class="line">                &lt;div className=<span class="string">&quot;game-info&quot;</span>&gt;</span><br><span class="line">                    &lt;div&gt;&#123;status&#125;&lt;/div&gt;</span><br><span class="line">                    &lt;ol&gt;&#123;moves&#125;&lt;/ol&gt;</span><br><span class="line">                &lt;/div&gt;</span><br><span class="line">            &lt;/div&gt;</span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ToyReact.render(<span class="xml"><span class="tag">&lt;<span class="name">Game</span> /&gt;</span></span>, <span class="built_in">document</span>.getElementById(<span class="string">&quot;root&quot;</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 判断胜者</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calculateWinner</span>(<span class="params">squares</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 获胜的序号</span></span><br><span class="line">    <span class="keyword">const</span> lines = [</span><br><span class="line">        [<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>],</span><br><span class="line">        [<span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>],</span><br><span class="line">        [<span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>],</span><br><span class="line">        [<span class="number">0</span>, <span class="number">3</span>, <span class="number">6</span>],</span><br><span class="line">        [<span class="number">1</span>, <span class="number">4</span>, <span class="number">7</span>],</span><br><span class="line">        [<span class="number">2</span>, <span class="number">5</span>, <span class="number">8</span>],</span><br><span class="line">        [<span class="number">0</span>, <span class="number">4</span>, <span class="number">8</span>],</span><br><span class="line">        [<span class="number">2</span>, <span class="number">4</span>, <span class="number">6</span>],</span><br><span class="line">    ]</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; lines.length; i++) &#123;</span><br><span class="line">        <span class="keyword">const</span> [a, b, c] = lines[i]</span><br><span class="line">        <span class="comment">// 有人获胜</span></span><br><span class="line">        <span class="keyword">if</span> (squares[a] &amp;&amp; squares[a] === squares[b] &amp;&amp; squares[a] === squares[c]) &#123;</span><br><span class="line">            <span class="keyword">return</span> squares[a]</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>还有样式，<code>main.html</code>中导入<code>main.css</code></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;en&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;main.css&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text/css&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;icon&quot;</span> <span class="attr">href</span>=<span class="string">&quot;favicon.ico&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1&quot;</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">title</span>&gt;</span>Tic tac toe<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;root&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p><code>main.css</code>中的样式</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">    <span class="attribute">font</span>: <span class="number">14px</span> <span class="string">&quot;Century Gothic&quot;</span>, Futura, sans-serif;</span><br><span class="line">    <span class="attribute">margin</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-tag">ol</span>,</span><br><span class="line"><span class="selector-tag">ul</span> &#123;</span><br><span class="line">    <span class="attribute">padding-left</span>: <span class="number">30px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.board-row</span>:after &#123;</span><br><span class="line">    clear: both;</span><br><span class="line">    <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">    <span class="attribute">display</span>: table;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.status</span> &#123;</span><br><span class="line">    <span class="attribute">margin-bottom</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.square</span> &#123;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#fff</span>;</span><br><span class="line">    <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#999</span>;</span><br><span class="line">    <span class="attribute">float</span>: left;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">24px</span>;</span><br><span class="line">    <span class="attribute">font-weight</span>: bold;</span><br><span class="line">    <span class="attribute">line-height</span>: <span class="number">34px</span>;</span><br><span class="line">    <span class="attribute">height</span>: <span class="number">34px</span>;</span><br><span class="line">    <span class="attribute">margin-right</span>: -<span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">margin-top</span>: -<span class="number">1px</span>;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">text-align</span>: center;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">34px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.square</span><span class="selector-pseudo">:focus</span> &#123;</span><br><span class="line">    <span class="attribute">outline</span>: none;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.kbd-navigation</span> <span class="selector-class">.square</span><span class="selector-pseudo">:focus</span> &#123;</span><br><span class="line">    <span class="attribute">background</span>: <span class="number">#ddd</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.game</span> &#123;</span><br><span class="line">    <span class="attribute">display</span>: flex;</span><br><span class="line">    <span class="attribute">flex-direction</span>: row;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.game-info</span> &#123;</span><br><span class="line">    <span class="attribute">margin-left</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="虚拟DOM的原理和关键实现"><a href="#虚拟DOM的原理和关键实现" class="headerlink" title="虚拟DOM的原理和关键实现"></a>虚拟DOM的原理和关键实现</h1><p>现在，我们拥有了基于实体dom的ToyReact，可喜可贺</p>
<p>✿✿ヽ(°▽°)ノ✿</p>
<p>但是，react的核心是虚拟dom，毕竟有了虚拟dom，我们的组件刷新才更高效嘛</p>
<p>那么，我们来实现这个功能吧</p>
<h2 id="1-实现虚拟dom树的创建及渲染"><a href="#1-实现虚拟dom树的创建及渲染" class="headerlink" title="1. 实现虚拟dom树的创建及渲染"></a>1. 实现虚拟dom树的创建及渲染</h2><p>要支持虚拟dom，我们需要重构ElementWrapper、TextWrapper以及Component，毕竟它们通过root这一实际dom进行渲染和更新。</p>
<h3 id="什么是虚拟DOM"><a href="#什么是虚拟DOM" class="headerlink" title="什么是虚拟DOM"></a>什么是虚拟DOM</h3><p>虚拟DOM本质上其实是对真实DOM的一种映射关系。它是一种以对象的形态来表示真实存在的DOM。举个例子:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;ul1&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">name</span>=<span class="string">&quot;1&quot;</span>&gt;</span>world !<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">name</span>=<span class="string">&quot;2&quot;</span>&gt;</span>world !<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">name</span>=<span class="string">&quot;3&quot;</span>&gt;</span>world !<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>上面的html代码如果以虚拟DOM的形态来表示的话, 那么就是:</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    type: <span class="string">&#x27;ul&#x27;</span>,</span><br><span class="line">  	props: &#123;      </span><br><span class="line">    	id: <span class="string">&#x27;ul1&#x27;</span></span><br><span class="line"> 	&#125;,</span><br><span class="line">	children: [</span><br><span class="line">		&#123; <span class="attr">type</span>: <span class="string">&#x27;li&#x27;</span>, <span class="attr">props</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;1&#x27;</span>&#125;, <span class="attr">children</span>: [<span class="string">&#x27;world !&#x27;</span>]&#125;,</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&#x27;li&#x27;</span>, <span class="attr">props</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;2&#x27;</span>&#125;, <span class="attr">children</span>: [<span class="string">&#x27;world !&#x27;</span>]&#125;,</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&#x27;li&#x27;</span>, <span class="attr">props</span>: &#123;<span class="attr">name</span>: <span class="string">&#x27;3&#x27;</span>&#125;, <span class="attr">children</span>: [<span class="string">&#x27;world !&#x27;</span>]&#125;,</span><br><span class="line">	]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>对于元素节点来说, 虚拟DOM应该包含三样东西:</p>
<ul>
<li>节点的类型(比如div, span, p)</li>
<li>节点上的props</li>
<li>节点的children</li>
</ul>
<p>然而对于文本节点来说, 它的类型是固定的, 唯一不同的就是他的内容了, 因此它的虚拟DOM就比较简单了</p>
<ul>
<li>节点的类型(text)</li>
<li>节点的内容(content)</li>
</ul>
<p>那么对应到ToyReact中的代码片段应该是<code>ElementWrapper</code> 和 <code>TextWrapper</code> 这两个类。</p>
<h3 id="1-1-ElementWrapper的vdom实现"><a href="#1-1-ElementWrapper的vdom实现" class="headerlink" title="1.1 ElementWrapper的vdom实现"></a>1.1 ElementWrapper的vdom实现</h3><p>在ElementWrapper中我们增加<code>vdom</code>的get</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">get vdom () &#123;</span><br><span class="line">	<span class="keyword">return</span> &#123;</span><br><span class="line">    	type: <span class="built_in">this</span>.type,</span><br><span class="line">        props: <span class="built_in">this</span>.props,</span><br><span class="line">        <span class="comment">// 拿到每个child的vdom</span></span><br><span class="line">        children: <span class="built_in">this</span>.children.map(<span class="function"><span class="params">child</span> =&gt;</span> child.vdom)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在setAtrribute方法中，我们存储了<code>this.props</code>，而在appendChild中，我们存储了<code>this.children</code>。这两种方法的逻辑，和Component有重合，<strong>所以ElementWrapper和TextWrapper可以继承Component</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ElementWrapper</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params">type</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(type)</span><br><span class="line">        <span class="built_in">this</span>.type = type</span><br><span class="line">        <span class="comment">// 创建根元素</span></span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createElement(type)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>和</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TextWrapper</span> <span class="keyword">extends</span> <span class="title">Component</span></span>&#123;</span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params">content</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(content)</span><br><span class="line">        <span class="built_in">this</span>.content = content</span><br><span class="line">        <span class="built_in">this</span>.root = <span class="built_in">document</span>.createTextNode(content)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-2-TextWrapper的vdom实现"><a href="#1-2-TextWrapper的vdom实现" class="headerlink" title="1.2 TextWrapper的vdom实现"></a>1.2 TextWrapper的vdom实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">get vdom () &#123;</span><br><span class="line">	<span class="keyword">return</span> &#123;</span><br><span class="line">    	type: <span class="string">&quot;#text&quot;</span>,</span><br><span class="line">        content: <span class="built_in">this</span>.content</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-3-Component的vdom实现"><a href="#1-3-Component的vdom实现" class="headerlink" title="1.3 Component的vdom实现"></a>1.3 Component的vdom实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">get vdom () &#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">this</span>.render().vdom</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-4-vdom到实体dom的patch"><a href="#1-4-vdom到实体dom的patch" class="headerlink" title="1.4 vdom到实体dom的patch"></a>1.4 vdom到实体dom的patch</h3><p>我们之前在ElementWrapper中设置的setAtrribute和appendChild方法，实际创建了需要渲染的dom，而实现vdom到实体dom的patch的过程，这两个方法可以删除，其逻辑可以合并到<code>[RENDER_TO_DOM]</code>方法中。 因为基于vdom，所以保存实际dom的<code>this.root</code>属性可以删去，实际dom只在<code>[RENDER_TO_DOM]</code>方法中存在</p>
<p>在删除setAtrribute和appendChild方法之后，<strong>因为 get vdom 的操作只返回了对象，并没有返回方法，无法重绘</strong>，所以ElementWrapper和TextWrapper的get vdom的正解应是：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ElementWrapper</span></span><br><span class="line">get vdom () &#123;</span><br><span class="line">	<span class="built_in">this</span>.vchildren = <span class="built_in">this</span>.children.map(<span class="function"><span class="params">child</span> =&gt;</span> child.vdom)</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">this</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// TextWrapper</span></span><br><span class="line"><span class="function"><span class="title">constructor</span>(<span class="params">content</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(content);</span><br><span class="line">    <span class="built_in">this</span>.type = <span class="string">&#x27;#text&#x27;</span>;</span><br><span class="line">    <span class="built_in">this</span>.content = content;</span><br><span class="line">    <span class="built_in">this</span>.root = <span class="built_in">document</span>.createTextNode(content)</span><br><span class="line">&#125;</span><br><span class="line">get vdom () &#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">this</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么，vdom的对象属性，就是ElementWrapper和TextWrapper中的属性。个人理解，这里将数据和行为进一步解耦。</p>
<h4 id="1-4-1-RENDER-TO-DOM-方法中patch的实现"><a href="#1-4-1-RENDER-TO-DOM-方法中patch的实现" class="headerlink" title="1.4.1  [RENDER_TO_DOM]方法中patch的实现"></a>1.4.1  [RENDER_TO_DOM]方法中patch的实现</h4><p>我们接下来先将注释掉的二个方法重新补上, 因为<code>setAttribute</code> 和 <code>appendChild</code>方法都是对实DOM的操作, 所以我打算把这两个 函数的实现全部放到 <code>RENDER_TO_DOM</code>函数中。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ElementWrapper</span></span><br><span class="line">[RENDER_TO_DOM] (range) &#123;</span><br><span class="line">	range.deleteContents()</span><br><span class="line">    </span><br><span class="line">	<span class="comment">// 创建实体dom，root</span></span><br><span class="line">    <span class="keyword">let</span> root = <span class="built_in">document</span>.createElement(<span class="built_in">this</span>.type)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// props内容抄写，setAttribute逻辑的实现</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> <span class="built_in">this</span>.props) &#123;</span><br><span class="line">    </span><br><span class="line">    	<span class="keyword">let</span> value = <span class="built_in">this</span>.props[name]</span><br><span class="line">        </span><br><span class="line">    	<span class="keyword">if</span> (name.match(<span class="regexp">/^on([\s\S]+)/</span>))&#123;</span><br><span class="line">            root.addEventListener(<span class="built_in">RegExp</span>.$1.replace(<span class="regexp">/^[\s\S]/</span>, <span class="function"><span class="params">c</span> =&gt;</span> c.toLowerCase()), value)</span><br><span class="line">        &#125; <span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (name === <span class="string">&quot;className&quot;</span>)&#123;</span><br><span class="line">                root.setAttribute(<span class="string">&quot;class&quot;</span>, value)</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                root.setAttribute(name, value)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// children的处理</span></span><br><span class="line">     <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> <span class="built_in">this</span>.children) &#123;</span><br><span class="line">     	</span><br><span class="line">        <span class="keyword">let</span> childRange = <span class="built_in">document</span>.createRange()</span><br><span class="line">        <span class="comment">// 将新增的元素置于range末尾</span></span><br><span class="line">        childRange.setStart(root, root.childNodes.length)</span><br><span class="line">        childRange.setEnd(root, root.childNodes.length)</span><br><span class="line">        child[RENDER_TO_DOM](childRange)</span><br><span class="line">     &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 挂载 root</span></span><br><span class="line">    range.insertNode(root)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="1-5-修复bug"><a href="#1-5-修复bug" class="headerlink" title="1.5 修复bug"></a>1.5 修复bug</h3><p>现在，我们的vdom树已经可以建起来啦~</p>
<p>不过还是留了一些尾巴，特别在于vchildren的处理上，这部分还需要优化，需要去掉Component的vchildren getter</p>
<p>对于ElementWrapper和TextWrapper，我们需要在在[RENDER_TO_DOM]方法中保存之前的range</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TextWrapper</span></span><br><span class="line">[RENDER_TO_DOM] (range) &#123;</span><br><span class="line">	<span class="built_in">this</span>._range = range</span><br><span class="line">    <span class="keyword">let</span> root = <span class="built_in">document</span>.createTextNode(<span class="built_in">this</span>.content)</span><br><span class="line">    range.deleteContents()</span><br><span class="line">    range.insertNode(root)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由于ElementWrapper和TextWrapper的[RENDER_TO_DOM]依旧采用先删除range，再渲染this.root的方式，会导致空range被吞，所以我们要优化[RENDER_TO_DOM]。而[RENDER_TO_DOM]的逻辑在不同位置都有使用，所以我们单独封装一个<code>repalceContent</code>方法</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">replaceContent</span> (<span class="params">range, node</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 将node插入range，此时node在range的最前位置</span></span><br><span class="line">	range.insertNode(node)</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// range挪到node之后</span></span><br><span class="line">    range.setStartAfter(node)</span><br><span class="line">    <span class="comment">// 清空range</span></span><br><span class="line">    range.deleteContents()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 重设range的位置</span></span><br><span class="line">    range.setStartBefore(node)</span><br><span class="line">    range.setEndAfter(node)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在我们来改进[RENDER_TO_DOM]中range的处理</p>
<p>第一部分的for循环其实做的就是 <code>setAttribute</code> 的事情, 将属性赋值到元素上, 第二部分的for循环做的事情则是通过递归的方式插入child.</p>
<p>那么如何将虚拟DOM与实DOM结合一起呢。其实很简单, 我们通过遍历虚拟的children, 来构造一颗虚拟的树。最终将这棵虚拟的树转化为实际的树。 那么对应到代码上应该如何修改呢？ 我们还是从 Component中的 <code>RENDER_TO_DOM</code>主体函数上着手, 我们仔细研读以下代码, 我们发现这边的children 还是实际的children, 并不是虚拟的children, 因此我们需要在Component类中定义一个vchildren。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">get</span> <span class="title">vchildren</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.children.map(<span class="function"><span class="params">child</span> =&gt;</span> child.vdom)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>同理我们遍历的时候就用vchildren进行遍历, 这样我们的这颗树就是虚拟的树。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> child <span class="keyword">of</span> <span class="built_in">this</span>.vchildren) &#123;</span><br><span class="line">    <span class="keyword">const</span> childRange = <span class="built_in">document</span>.createRange();</span><br><span class="line">    childRange.setStart(root, root.childNodes.length);</span><br><span class="line">    childRange.setEnd(root, root.childNodes.length);</span><br><span class="line">    child[RENDER_TO_DOM](childRange);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>完整的虚拟dom这块实现的代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TextWrapper</span></span><br><span class="line">[RENDER_TO_DOM] (range) &#123;</span><br><span class="line">	<span class="keyword">let</span> root = <span class="built_in">document</span>.createTextNode(<span class="built_in">this</span>.content)</span><br><span class="line">	<span class="built_in">this</span>._range = range</span><br><span class="line">    replaceContent(range, root)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ElementWrapper</span></span><br><span class="line">get vdom () &#123;</span><br><span class="line">    <span class="built_in">this</span>.vchildren = <span class="built_in">this</span>.children.map(<span class="function"><span class="params">child</span> =&gt;</span> child.vdom)</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">this</span></span><br><span class="line">&#125;</span><br><span class="line">[RENDER_TO_DOM] (range) &#123;</span><br><span class="line">	<span class="built_in">this</span>._range = range</span><br><span class="line">	<span class="comment">// 通过replaceContent代替初始时range.deleteContents()</span></span><br><span class="line">	<span class="comment">// range.deleteContents()</span></span><br><span class="line">	</span><br><span class="line">    <span class="keyword">let</span> root = <span class="built_in">document</span>.createElement(<span class="built_in">this</span>.type)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> <span class="built_in">this</span>.props) &#123;</span><br><span class="line">    	<span class="keyword">let</span> value = <span class="built_in">this</span>.props[name]</span><br><span class="line">    	<span class="keyword">if</span> (name.match(<span class="regexp">/^on([\s\S]+)/</span>))&#123;</span><br><span class="line">            root.addEventListener(<span class="built_in">RegExp</span>.$1.replace(<span class="regexp">/^[\s\S]/</span>, <span class="function"><span class="params">c</span> =&gt;</span> c.toLowerCase()), value)</span><br><span class="line">        &#125; <span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (name === <span class="string">&quot;className&quot;</span>)&#123;</span><br><span class="line">                root.setAttribute(<span class="string">&quot;class&quot;</span>, value)</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                root.setAttribute(name, value)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">     <span class="keyword">for</span> (<span class="keyword">let</span> child <span class="keyword">of</span> <span class="built_in">this</span>.vchildren) &#123;</span><br><span class="line">        <span class="keyword">let</span> childRange = <span class="built_in">document</span>.createRange()</span><br><span class="line">        childRange.setStart(root, root.childNodes.length)</span><br><span class="line">        childRange.setEnd(root, root.childNodes.length)</span><br><span class="line">        child[RENDER_TO_DOM](childRange)</span><br><span class="line">     &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 完成root的挂载</span></span><br><span class="line">    replaceContent(range, root)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="2-vdom比对的实现"><a href="#2-vdom比对的实现" class="headerlink" title="2. vdom比对的实现"></a>2. vdom比对的实现</h2><p>在拥有了vdom树的创建能力之后，我们的<code>rerender</code>函数可以退休了，代替它的是vdom更新，在Component基类中实现</p>
<p>在Component的[RENDER_TO_DOM]方法中，我们需要先更新vdom，再渲染</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Component</span></span><br><span class="line">[RENDER_TO_DOM] (range) &#123;</span><br><span class="line">	<span class="comment">// 保存range和vdom</span></span><br><span class="line">    <span class="built_in">this</span>._range = range</span><br><span class="line">    <span class="comment">// 由于this.vdom是getter，所以会重新调用组件的render方法，返回新的vdom，实现vdom更新</span></span><br><span class="line">    <span class="built_in">this</span>._vdom = <span class="built_in">this</span>.vdom</span><br><span class="line">    <span class="comment">// 渲染旧的vdom</span></span><br><span class="line">    <span class="built_in">this</span>._vdom[RENDER_TO_DOM](range)</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="什么是Diff算法"><a href="#什么是Diff算法" class="headerlink" title="什么是Diff算法"></a>什么是Diff算法</h3><p>Diff算法其实是通过比对新旧虚拟DOM树,然后将不同的部分渲染到页面中,从而达到最小化更新DOM的目的。</p>
<p>以下图DOM为例:</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407083210.png" alt="DOM"></p>
<p>Diff算法采用按照深度遍历规则遍历的, 因此遍历的过程如下:</p>
<ul>
<li><ol>
<li>对比节点1(没有变化)</li>
</ol>
</li>
<li><ol>
<li>对比节点2(没有变化)</li>
</ol>
</li>
<li><ol>
<li>对比节点4(没有变化)</li>
</ol>
</li>
<li><ol>
<li>对比节点5<code>(节点5被移除, 记录一个删除的操作)</code></li>
</ol>
</li>
<li><ol>
<li>对比节点3(没有变化)</li>
</ol>
</li>
<li><ol>
<li>对比节点3的children<code>(新增节点5, 记录一个新增操作)</code></li>
</ol>
</li>
</ul>
<p>因此在实际渲染的过程中, 会执行节点5的删除和新增操作, 其余节点不会发生任何变化。</p>
<p>Diff算法在前面我们已经稍微介绍过了, 我们不会跟React一样去实现他的diff算法, 因为这不是本文的重点。本文的重点旨在让大家理解Diff算法是如何贯穿于虚拟DOM的。但是我们会尽可能的多考虑Diff重绘的情况。那么哪几种情况会导致我们的DOM树重绘制呢?</p>
<ul>
<li>节点的类型不同</li>
</ul>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220406223503.png" alt="节点的类型不同"></p>
<ul>
<li>新老节点的props值不同</li>
</ul>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407132424.png" alt="新老节点的props值不同"></p>
<ul>
<li>新节点的props少于老节点的props</li>
</ul>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407133433.png" alt="新节点的props少于老节点的props"></p>
<ul>
<li>文本节点的内容不同</li>
</ul>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407133532.png" alt="文本节点的内容不同"></p>
<h3 id="2-1-vdom更新"><a href="#2-1-vdom更新" class="headerlink" title="2.1 vdom更新"></a>2.1 vdom更新</h3><p>在Component的unpdate方法中，我们实现vdom的更新。我们需要做的就是遍历我们的虚拟DOM树.首先判断新的虚拟DOM节点与老的虚拟DOM节点 是否一样, 如果一样那么就直接替换<code>range</code>, 并且通过递归的方式去比对子节点。 如果节点不一样, 那么就更新当前节点下的range, 从而达到部分更新的效果。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">update () &#123;</span><br><span class="line">	 <span class="keyword">let</span> update = <span class="function">(<span class="params">oldNode, newNode</span>) =&gt;</span> &#123;</span><br><span class="line">     <span class="comment">// 更新为newNode</span></span><br><span class="line">     &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 保存新的vdom</span></span><br><span class="line">    <span class="keyword">let</span> vdom = <span class="built_in">this</span>.vdom</span><br><span class="line">    <span class="comment">// 对比vdom</span></span><br><span class="line">    update(<span class="built_in">this</span>._vdom, vdom)</span><br><span class="line">    <span class="comment">// 重新赋值</span></span><br><span class="line">    <span class="built_in">this</span>._vdom = vdom</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么在实现更新vdom功能前，我们需要对比根节点，细化vdom更新范围。</p>
<h3 id="2-2-简单的-dom-diff-算法"><a href="#2-2-简单的-dom-diff-算法" class="headerlink" title="2.2 简单的 dom diff 算法"></a>2.2 简单的 dom diff 算法</h3><p>我们的节点更新逻辑如下：</p>
<ul>
<li>根节点的type是否一致，不一致，则更新为新的根节点</li>
<li>根节点的props是否一致，不一致，则更新为新的根节点</li>
<li>根节点的children是否一致，不一致，则更新为新的根节点</li>
<li>对于文本节点，content不一致，则更新为新节点</li>
</ul>
<p>我们采用<del>最土，最傻瓜</del>最简练的方式更新dom</p>
<ul>
<li>只对比相同位置的vdom</li>
<li>采用直接替换的方式更新节点</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">update () &#123;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">let</span> isSameNode = <span class="function">(<span class="params">oldNode, newNode</span>) =&gt;</span> &#123;</span><br><span class="line"></span><br><span class="line">		<span class="comment">// type不同，则为不同节点</span></span><br><span class="line">		<span class="keyword">if</span> (oldNode.type !== newNode.type) &#123;</span><br><span class="line">    		<span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    	&#125;</span><br><span class="line">    </span><br><span class="line">    	<span class="comment">// props不同，则为不同节点</span></span><br><span class="line">    	<span class="keyword">for</span> (<span class="keyword">let</span> name <span class="keyword">in</span> newNode.props) &#123;</span><br><span class="line">    		<span class="comment">// 属性值要相同</span></span><br><span class="line">        	<span class="keyword">if</span> (newNode.props[name] !== oldNode.props[name]) &#123;</span><br><span class="line">        		<span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        	&#125;</span><br><span class="line">    	&#125;</span><br><span class="line">    	<span class="comment">// props的长度不相同，节点不相同</span></span><br><span class="line">    	<span class="keyword">if</span> (<span class="built_in">Object</span>.keys(oldNode.props).length &gt; <span class="built_in">Object</span>.keys(newNode.props)) &#123;</span><br><span class="line">    		<span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    	&#125;</span><br><span class="line">    </span><br><span class="line">    	<span class="comment">// 文本节点，比对content</span></span><br><span class="line">    	<span class="keyword">if</span> (newNode.type === <span class="string">&quot;#text&quot;</span>) &#123;</span><br><span class="line">    		<span class="keyword">if</span> (newNode.content !== oldNode.content) &#123;</span><br><span class="line">    			<span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    		&#125;</span><br><span class="line">    	&#125;</span><br><span class="line">    </span><br><span class="line">		<span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">	&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">let</span> update = <span class="function">() =&gt;</span> &#123;&#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 保存新的vdom</span></span><br><span class="line">    <span class="keyword">let</span> vdom = <span class="built_in">this</span>.vdom</span><br><span class="line">    <span class="comment">// 对比vdom</span></span><br><span class="line">    update(<span class="built_in">this</span>._vdom, vdom)</span><br><span class="line">    <span class="comment">// 重新赋值</span></span><br><span class="line">    <span class="built_in">this</span>._vdom = vdom</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>还差最后一步, 每次更新后, 还需要将虚拟DOM树也更新, 为下次Diff做准备。我们定义一个<code>vdom</code> 来接收最新的虚拟DOM树, 执行完更新虚拟的函数后, 我们需要将老的虚拟dom树(this._vdom)换成更新完后 的虚拟dom树。至此整个更新流程就完成了, 我们来查看一下最后的效果。</p>
<h3 id="2-3-newNode更新"><a href="#2-3-newNode更新" class="headerlink" title="2.3 newNode更新"></a>2.3 newNode更新</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">update () &#123;</span><br><span class="line">	<span class="keyword">let</span> isSameNode = <span class="function">(<span class="params">oldNode, newNode</span>) =&gt;</span> &#123;</span><br><span class="line">    	... ...</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">let</span> update = <span class="function">(<span class="params">oldNode, newNode</span>) =&gt;</span> &#123;</span><br><span class="line">    </span><br><span class="line">    	<span class="comment">// 根节点不同，则全部重新渲染</span></span><br><span class="line">    	<span class="keyword">if</span> (!isSameNode(oldNode, newNode)) &#123;</span><br><span class="line">        	<span class="comment">// 替换oldNode</span></span><br><span class="line">        	newNode[RENDER_TO_DOM](oldNode._range)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        newNode._range = oldNode._range</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// children的处理</span></span><br><span class="line">        <span class="comment">// 因为children属性是实体dom，所以我们要拿到vchildren</span></span><br><span class="line">        <span class="keyword">let</span> newChildren = newNode.vchildren</span><br><span class="line">        <span class="keyword">let</span> oldChildren = oldNode.vchildren</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (!newChildren || !newChildren.length) &#123;</span><br><span class="line">        	<span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 记录oldChildren的尾部位置</span></span><br><span class="line">        <span class="keyword">let</span> tailRange = oldChildren[oldChildren.length - <span class="number">1</span>]._range</span><br><span class="line">        <span class="comment">// 两个数组一起循环，所以不用 for of循环</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; newChildren.length; i++) &#123;</span><br><span class="line">        	<span class="keyword">let</span> newChild = newChildren[i]</span><br><span class="line">            <span class="keyword">let</span> oldChild = oldChildren[i]</span><br><span class="line">            <span class="keyword">if</span> (i &lt; oldChildren.length) &#123;</span><br><span class="line">            	update(oldChild, newChild)</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 如果newChild比oldChild元素多，我们需要在newChild进行节点插入</span></span><br><span class="line">                <span class="comment">// 创建一个需要插入的range</span></span><br><span class="line">                <span class="keyword">let</span> range = <span class="built_in">document</span>.createRange()</span><br><span class="line">                range.setStart(tailRange.endContainer, tailRange.endOffset)</span><br><span class="line">                range.setEnd(tailRange.endContainer, tailRange.endOffset)</span><br><span class="line">                newChild[RENDER_TO_DOM](range)</span><br><span class="line">                tailRange = range</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">let</span> vdom = <span class="built_in">this</span>.vdom</span><br><span class="line">    <span class="comment">// 更新vdom</span></span><br><span class="line">    update(<span class="built_in">this</span>._vdom, vdom)</span><br><span class="line">    <span class="comment">// 重新赋值, 此时的“旧vdom”是vdom</span></span><br><span class="line">    <span class="built_in">this</span>._vdom = vdom</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>之后在setState中调用update，即可实现虚拟dom的更新。</p>
<p>至此，塑料版react toyReact就完成啦✿✿ヽ(°▽°)ノ✿</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407173725.gif" alt="塑料版ToyReact使用"></p>
<p>我们发现DOM树已经不再是重头绘制了。它的更新范围已经缩小在了 <code>Board</code> 类对应的DOM树范围内了。 但是这边有一个小问题，为什么我们不能单个button格子更新呢？我打算用debug的方式来说明这个问题。 首先，在 <code>isSameNode</code> 函数上打一个断点，方便我们调试.</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> isSameNode = <span class="function">(<span class="params">oldNode, newNode</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">debugger</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(oldNode.type !== newNode.type) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> newNode.props) &#123;</span><br><span class="line">        <span class="keyword">if</span> (oldNode.props[key] !== newNode.props[key]) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">Object</span>.keys(oldNode.props).length &gt;  <span class="built_in">Object</span>.keys(newNode.props).length)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(newNode.type === <span class="string">&quot;#text&quot;</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span>(newNode.content !== oldNode.content) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>点击左上方的格子，打开浏览器调试模式，程序即可进入debugger模式。我们直接进入到最后一次比较格子的地方。</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407174623.png" alt="oldNode"></p>
<p>老的虚拟DOM的<code>content</code> 是空值，按照正常逻辑此时的对应的新的虚拟DOM中的content值应该是一个<code>x</code>。我们来校验一下结果。</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407175146.png" alt="newNode"></p>
<p>OK，那么我们接下去需要知道的是，它是在比较什么地方的时候，出现了不同，导致重绘的。我们发现在比对新老节点的props的时候，发生了值不一样的情况。而不一样的key，是 <code>onClick </code>。</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407180207.png" alt="onClick导致重绘"></p>
<p>调试到这里我们应该明白了为什么它重绘的区域与我们想象的不一样了。在比对事件的时候, 我们每次都会实例化一个新的事件函数。这就导致了我们的ToyReact处理不了关于事件调度方面的diff了。那么如果想要达到React那样, 只更新某个节点 这样的效果的话, 我们可以采取最残暴的手法, 直接忽略所有的事件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> newNode.props) &#123;</span><br><span class="line">    <span class="keyword">if</span> (oldNode.props[key] !== newNode.props[key]) &#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">typeof</span> newNode.props[key] !== <span class="string">&#x27;function&#x27;</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们继续看看, 最后的效果如何?</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20220407180613.gif" alt="忽略所有的事件"></p>
<p>It is Cool! 我们也做到了只更新某个节点的效果了。 不过大家乐呵乐呵就行, 学技术还是得看React官方源码, 哈哈。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>借助webpack，我们实现了ToyReact，一个塑料react🐶</p>
<h2 id="1-JSX-语法解析"><a href="#1-JSX-语法解析" class="headerlink" title="1. JSX 语法解析"></a>1. JSX 语法解析</h2><p>JSX 是 JavaScript 与 XML 相结合的一种格式。React发明了JSX，实现了利用HTML 语法来创建虚拟DOM。遇到<code>&lt;</code>时，JSX将起作为HTML解析，遇到 <code>&#123;</code>JSX将其作为JavaScript解析。</p>
<p><strong>JSX语法并不是直接把JSX渲染到页面</strong>，而是在React内部先将JSX转换成createElement形式，再去渲染的，同时JSX在编译成JavaScript代码的时候进行了一定的优化，保证了React的更高执行效率。</p>
<p>在ToyReact中，我们通过设置webpack的 ** @babel/plugin-transform-react-jsx** 插件，重新定义了jsx生成dom的函数（并没有实现jsx语法本身）</p>
<p>↓↓↓ webpack 配置如下 ↓↓↓</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="built_in">module</span>: &#123;</span><br><span class="line">        rules: [</span><br><span class="line">            &#123;</span><br><span class="line">                test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">                use: &#123;</span><br><span class="line">                    loader: <span class="string">&#x27;babel-loader&#x27;</span>,</span><br><span class="line">                    <span class="comment">// 配置babel-loader</span></span><br><span class="line">                    options: &#123;</span><br><span class="line">                        presets: [<span class="string">&#x27;@babel/preset-env&#x27;</span>],</span><br><span class="line">                        <span class="comment">// 这样打包后的‘React.createElement就会变成‘createElement’</span></span><br><span class="line">                        <span class="comment">// 我们定义的 createElement 函数将会覆盖 React.createElement 方法</span></span><br><span class="line">                        plugins: [[<span class="string">&#x27;@babel/plugin-transform-react-jsx&#x27;</span>, &#123;<span class="attr">pragma</span>: <span class="string">&#x27;ToyReact.createElement&#x27;</span>&#125;]]</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        ]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="2-ToyReact生命周期"><a href="#2-ToyReact生命周期" class="headerlink" title="2. ToyReact生命周期"></a>2. ToyReact生命周期</h2><p>在Component class里有 <a href="https://link.juejin.cn/?target=">RENDER_TO_DOM</a> 和 update() 两个函数，对应React生命周期，就是在<strong>挂载前</strong>需要的操作和<strong>更新</strong>时需要的操作。</p>
<p>在<strong>挂载</strong>之前：通过setAttribute添加自定义的属性，addEventListener添加事件；然后就会执行一次render</p>
<p>如果有<strong>更新</strong>操作，就会在update()内会通过对比对更新的元素进行替换；再次render</p>
<h3 id="React生命周期函数"><a href="#React生命周期函数" class="headerlink" title="React生命周期函数"></a>React生命周期函数</h3><ul>
<li><del>组件将要挂载时触发的函数：componentWillMount</del></li>
<li>组件挂载完成时触发的函数：componentDidMount</li>
<li>是否要更新数据时触发的函数：shouldComponentUpdate</li>
<li><del>将要更新数据时触发的函数：componentWillUpdate</del></li>
<li>数据更新完成时触发的函数：componentDidUpdate</li>
<li>组件将要销毁时触发的函数：componentWillUnmount</li>
<li>父组件中改变了props传值时触发的函数：<del>componentWillReceiveProps</del></li>
</ul>
<h3 id="React16废弃生命周期"><a href="#React16废弃生命周期" class="headerlink" title="React16废弃生命周期"></a>React16废弃生命周期</h3><p>React16废弃了三个生命周期：componentWillMount、componentWillReceiveProps、componentWillUpdate</p>
<p>废弃原因：在React16的Fiber架构中，调和过程会多次执行will周期，不再是一次执行，所以这些周期失去了原有的意义。此外，由于周期会多次执行， 在周期中设置setState或dom操作，会触发多次重绘，影响性能，也会导致数据错乱</p>
<p>componentWillMount和componentWillUpdate在每一个组件render之前都会去调用componentWillMount()，可以在服务端调用也可以在浏览器端调用，<strong>如果有异步请求，不推荐在此时请求数据，因为在render前并不会返回数据</strong>。</p>
<p>componentWillUpdate()在组件将要更新数据的时候都会触发一次，执行更新操作。</p>
<h3 id="React16新增2个生命周期："><a href="#React16新增2个生命周期：" class="headerlink" title="React16新增2个生命周期："></a>React16新增2个生命周期：</h3><ul>
<li>getDerivedStateFromProps：16.3是在props变化时触发，16.4则改为在每次组件渲染器调用</li>
<li>getSnapshotBeforeUpdate：在render之后，更新DOM之前，state已更新</li>
</ul>
<p><strong>getDerivedStateFromProps</strong>周期有些难用：</p>
<ol>
<li>触发时机频繁，16.3是在props变化时触发，16.4则改为在每次组件渲染器调用，</li>
</ol>
<p>即无论props变化，组件自己setState，父组件render 都会触发 2. 静态方法，本意是隔离访问组件实例，却会造成访问组件的数据和方法十分不便，难以进行数据比较 3. 不能setState，而是返回一个对象来更新state，使用不便，也可能触发多次更新状态</p>
<p><strong>getSnapshotBeforeUpdate</strong>周期在Fiber架构中，只会调用一次，实现了类似willMount的效果。可以用来读取DOM，强制用户只能在mount阶段读取DOM。</p>
<h2 id="3-ToyReact虚拟DOM"><a href="#3-ToyReact虚拟DOM" class="headerlink" title="3. ToyReact虚拟DOM"></a>3. ToyReact虚拟DOM</h2><p>React将DOM抽象为虚拟DOM，用JavaScript模拟一颗DOM树，放在浏览器内存中。当变更时，虚拟DOM使用DIFF算法进行新旧虚拟DOM的比较，将变更放到变更队列中，最终只把变化的部分重新渲染，从而提高渲染效率。</p>
<p>在需要<strong>频繁微改动DOM时</strong>，直接修改DOM会引起页面的多次渲染，影响性能。而使用虚拟DOM的时候，先对比差异，再修改JS对象(生成虚拟DOM)，最后把生成的DOM结构插入到页面中，从而减少渲染次数，提升整个页面的渲染效率。</p>
<h3 id="Range"><a href="#Range" class="headerlink" title="Range"></a>Range</h3><p>ToyReact的虚拟DOM实现，借助了Range对象</p>
<p>Range对象表示文档的连续范围区域，相当于<strong>高亮选区</strong>。一个Range的开始点和结束点可以是任意的，开始点和结束点也可以是一样的(空Range)</p>
<p>Range的应用常见于富文本编辑器的相关操作</p>
<p>使用Range时，首先会创建一个range对象(createRange)，将指定节点的终点位置指定为Range对象所代表区域的起点位置(setStartAfter)；紧接着将指定的节点插入到某个Range对象所代表的区域中，插入位置为Range对象所代表区域的起点位置，如果该节点已经存在于页面中，该节点将被移动到Range对象代表的区域的起点处(insertNode)。</p>
<p>Range对象API官网：</p>
<p><a href="https://link.juejin.cn/?target=https://developer.mozilla.org/en-US/docs/Web/API/Range%EF%BC%9B">developer.mozilla.org/en-US/docs/…</a></p>
<p>Range的使用博客：</p>
<p><a href="https://link.juejin.cn/?target=https://laixiazheteng.com/article/page/id/uMXiMenCofsB">laixiazheteng.com/article/pag…</a></p>
<p><a href="https://link.juejin.cn/?target=https://blog.csdn.net/qq_21119773/article/details/51627382">blog.csdn.net/qq_21119773…</a></p>

    </div>

    
    
    
      
<div>
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
</div>
        

  <div class="followme">
    <p>欢迎关注我的其它发布渠道</p>

    <div class="social-list">

        <div class="social-item">
          <a target="_blank" class="social-link" href="/atom.xml">
            <span class="icon">
              <i class="fa fa-rss"></i>
            </span>

            <span class="label">RSS</span>
          </a>
        </div>
    </div>
  </div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/React/" rel="tag"><i class="fa fa-tag"></i> React</a>
              <a href="/tags/%E5%89%8D%E7%AB%AF/" rel="tag"><i class="fa fa-tag"></i> 前端</a>
              <a href="/tags/%E8%BD%AE%E5%AD%90/" rel="tag"><i class="fa fa-tag"></i> 轮子</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2022/02/07/%E8%A5%BF%E6%B9%96%E8%B5%8F%E9%9B%AA%E2%80%94%E2%80%94%E5%A4%B1%E8%B4%A5%E5%AD%A6%E9%9A%8F%E6%83%B3/" rel="prev" title="西湖赏雪——失败学随想">
      <i class="fa fa-chevron-left"></i> 西湖赏雪——失败学随想
    </a></div>
      <div class="post-nav-item">
    <a href="/2022/04/12/%E7%BA%A2%E5%B1%B1%E5%8A%A8%E7%89%A9%E5%9B%AD%E4%B8%80%E6%97%A5%E6%B8%B8%E9%9A%8F%E6%83%B3/" rel="next" title="红山动物园一日游随想">
      红山动物园一日游随想 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#React%E6%A1%86%E6%9E%B6%E8%83%8C%E5%90%8E%E7%9A%84%E6%A0%B8%E5%BF%83%E6%9C%BA%E7%90%86"><span class="nav-text">React框架背后的核心机理</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-%E9%A1%B9%E7%9B%AE%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="nav-text">1. 项目初始化</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-%E9%85%8D%E7%BD%AEwebpack%E7%8E%AF%E5%A2%83"><span class="nav-text">2. 配置webpack环境</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-%E5%AE%89%E8%A3%85webpack"><span class="nav-text">1.安装webpack</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E9%85%8D%E7%BD%AEwebpack"><span class="nav-text">2.配置webpack</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-%E9%85%8D%E7%BD%AEbabel"><span class="nav-text">3.配置babel</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-%E5%AE%9E%E7%8E%B0toyReact"><span class="nav-text">3. 实现toyReact</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#3-1-%E6%99%AE%E9%80%9A%E8%8A%82%E7%82%B9%E7%9A%84wrapper%E5%AE%9E%E7%8E%B0"><span class="nav-text">3.1 普通节点的wrapper实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-2-%E5%AE%9E%E7%8E%B0Component%E7%B1%BB"><span class="nav-text">3.2 实现Component类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-3-%E5%AE%9E%E7%8E%B0render"><span class="nav-text">3.3 实现render</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-4-%E9%87%8D%E6%9E%84createElement"><span class="nav-text">3.4 重构createElement</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-5-main-js%E6%96%87%E4%BB%B6"><span class="nav-text">3.5 main.js文件</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-%E4%B8%80%E4%BA%9B%E7%AC%94%E8%AE%B0"><span class="nav-text">4. 一些笔记</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%B8%BAtoy-react%E6%B7%BB%E5%8A%A0%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="nav-text">为toy-react添加生命周期</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-state%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="nav-text">1. state的实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-ToyReact-dom%E6%9B%B4%E6%96%B0"><span class="nav-text">2. ToyReact dom更新</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#range%E7%9A%84%E5%AE%9A%E4%B9%89%E5%92%8C-API%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8"><span class="nav-text">range的定义和 API的简单使用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-%E9%87%8D%E5%86%99Component%E7%9A%84get-root"><span class="nav-text">2.1 重写Component的get root</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-%E9%87%8D%E5%86%99TextWrapper%E5%92%8CElementWrapper"><span class="nav-text">2.2 重写TextWrapper和ElementWrapper</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-%E6%9B%B4%E6%96%B0render%E5%87%BD%E6%95%B0"><span class="nav-text">2.3 更新render函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-%E6%94%AF%E6%8C%81%E9%87%8D%E6%96%B0%E7%BB%98%E5%88%B6dom"><span class="nav-text">2.4 支持重新绘制dom</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-%E6%94%AF%E6%8C%81%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A"><span class="nav-text">2.5 支持自定义事件事件绑定</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-%E5%AE%9E%E7%8E%B0setState"><span class="nav-text">3. 实现setState</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-%E9%9B%86%E6%88%90React%E5%AE%98%E7%BD%91%E7%A4%BA%E4%BE%8BTicTacToe-demo"><span class="nav-text">4. 集成React官网示例TicTacToe demo</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#4-1-insertChildren%E6%94%AF%E6%8C%81child%E4%B8%BAnull"><span class="nav-text">4.1 insertChildren支持child为null</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-2-className%E7%9A%84%E7%BB%91%E5%AE%9A"><span class="nav-text">4.2 className的绑定</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4-3-%E4%BF%AE%E5%A4%8D%E7%94%B1%E4%BA%8Erange%E8%A2%AB%E5%90%9E%E5%AF%BC%E8%87%B4%E7%9A%84%E9%97%AE%E9%A2%98"><span class="nav-text">4.3 修复由于range被吞导致的问题</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E8%99%9A%E6%8B%9FDOM%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%85%B3%E9%94%AE%E5%AE%9E%E7%8E%B0"><span class="nav-text">虚拟DOM的原理和关键实现</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-%E5%AE%9E%E7%8E%B0%E8%99%9A%E6%8B%9Fdom%E6%A0%91%E7%9A%84%E5%88%9B%E5%BB%BA%E5%8F%8A%E6%B8%B2%E6%9F%93"><span class="nav-text">1. 实现虚拟dom树的创建及渲染</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E8%99%9A%E6%8B%9FDOM"><span class="nav-text">什么是虚拟DOM</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-1-ElementWrapper%E7%9A%84vdom%E5%AE%9E%E7%8E%B0"><span class="nav-text">1.1 ElementWrapper的vdom实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-TextWrapper%E7%9A%84vdom%E5%AE%9E%E7%8E%B0"><span class="nav-text">1.2 TextWrapper的vdom实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-3-Component%E7%9A%84vdom%E5%AE%9E%E7%8E%B0"><span class="nav-text">1.3 Component的vdom实现</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-4-vdom%E5%88%B0%E5%AE%9E%E4%BD%93dom%E7%9A%84patch"><span class="nav-text">1.4 vdom到实体dom的patch</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1-4-1-RENDER-TO-DOM-%E6%96%B9%E6%B3%95%E4%B8%ADpatch%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="nav-text">1.4.1  [RENDER_TO_DOM]方法中patch的实现</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-5-%E4%BF%AE%E5%A4%8Dbug"><span class="nav-text">1.5 修复bug</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-vdom%E6%AF%94%E5%AF%B9%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="nav-text">2. vdom比对的实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AFDiff%E7%AE%97%E6%B3%95"><span class="nav-text">什么是Diff算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-vdom%E6%9B%B4%E6%96%B0"><span class="nav-text">2.1 vdom更新</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-%E7%AE%80%E5%8D%95%E7%9A%84-dom-diff-%E7%AE%97%E6%B3%95"><span class="nav-text">2.2 简单的 dom diff 算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-newNode%E6%9B%B4%E6%96%B0"><span class="nav-text">2.3 newNode更新</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-text">总结</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-JSX-%E8%AF%AD%E6%B3%95%E8%A7%A3%E6%9E%90"><span class="nav-text">1. JSX 语法解析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-ToyReact%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="nav-text">2. ToyReact生命周期</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#React%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%87%BD%E6%95%B0"><span class="nav-text">React生命周期函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#React16%E5%BA%9F%E5%BC%83%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="nav-text">React16废弃生命周期</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#React16%E6%96%B0%E5%A2%9E2%E4%B8%AA%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%EF%BC%9A"><span class="nav-text">React16新增2个生命周期：</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-ToyReact%E8%99%9A%E6%8B%9FDOM"><span class="nav-text">3. ToyReact虚拟DOM</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Range"><span class="nav-text">Range</span></a></li></ol></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="hxy"
      src="/images/Robben.gif">
  <p class="site-author-name" itemprop="name">hxy</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">80</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">8</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">120</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/huxingyi1997" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;huxingyi1997" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:huxingyi1997@zju.edu.cn" title="E-Mail → mailto:huxingyi1997@zju.edu.cn" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-frog"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">hxy</span>
</div>

<div class="theme-info">
  <div class="powered-by"></div>
  <span class="post-count">博客全站共1039.2k字</span>
</div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/lozad@1/dist/lozad.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : true,
      appId      : 'pQsO3ySbU4VtWN2j1FLA74Ha-gzGzoHsz',
      appKey     : 'QYacMDY2VY7Wazprg1X6FiUv',
      placeholder: "Just go go",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : false,
      lang       : 'zh-cn' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

  
  <!-- 动态背景特效 -->
  <!-- 樱花特效 -->
    <script async src="/js/src/sakura.js"></script>
    <script async src="/js/src/fairyDustCursor.js"></script>
</body>
</html>
